diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba2098..6165555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# v1.30.1 + +### Fixed + +- **Test Email always failed with `Email.send is not a function`.** `models/email.js` exports `{Mail}`; the handler required the module and called `.send` on it directly. Every other caller destructures it. The button could never have worked. +- **Test SMS failed with `Unexpected token '<', " 0) { const inst = instances[0]; const manifest = registry.getManifest(inst.pluginType); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index e10fbe3..bd5e400 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "t42-sso-manager", - "version": "1.30.0", + "version": "1.30.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "t42-sso-manager", - "version": "1.30.0", + "version": "1.30.1", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^7.3.0", diff --git a/nodejs/package.json b/nodejs/package.json index 1938bec..32668d1 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "t42-sso-manager", - "version": "1.30.0", + "version": "1.30.1", "description": "A very simple LDAP management and SSO system", "author": [ { diff --git a/nodejs/routes/api_conf.js b/nodejs/routes/api_conf.js index 03deea7..525f5d9 100644 --- a/nodejs/routes/api_conf.js +++ b/nodejs/routes/api_conf.js @@ -136,15 +136,25 @@ router.post('/test-email', async (req, res, next) => { return res.status(400).json({ error: 'Recipient email address is required' }); } - // Use the email model to send the test message - const Email = require('../models/email'); + // Send through the SAME sender every other feature uses (password reset, + // invites, OTP-by-email, notifications). A "test" that reimplements + // delivery proves nothing about whether real mail works. + // + // models/email.js exports `{Mail}`; requiring the module and calling + // `.send` on it directly -- as this did -- always threw + // "Email.send is not a function", so the button could never succeed. + const { Mail } = require('../models/email'); const testSubject = subject || 'SSO Manager Test Email'; const testBody = body || `
This is a test email from SSO Manager.
If you received this, your SMTP configuration is working correctly.
Sent at: ${new Date().toISOString()}
`; - await Email.send(to, testSubject, testBody); + await Mail.send(to, testSubject, testBody); res.json({ success: true, message: `Test email sent to ${to}` }); } catch(err) { - next(err); + // A failed test is almost always a misconfiguration (wrong host, refused + // connection, bad credentials) -- the operator's to fix, and something the + // UI should be able to show them. Surfacing it as a 400 with the reason + // beats an opaque 500 carrying a raw stack-trace name. + return res.status(400).json({ error: err.message || 'Failed to send test email' }); } }); @@ -156,38 +166,37 @@ router.post('/test-sms', async (req, res, next) => { return res.status(400).json({ error: 'Recipient phone number is required' }); } + // Send through models/sms.js -- the same path every real SMS takes. It + // prefers a configured messaging plugin and falls back to VoIP.ms, and it + // normalizes the destination to E.164 digits. + // + // This used to POST to `https://api.voip.ms/v1.0/sms/send` with Basic auth. + // No such endpoint exists: VoIP.ms's REST API is a GET against + // `https://voip.ms/api/v1/rest.php` with `api_username`/`api_password` and + // `method=sendSMS`. The fabricated URL returned an HTML page, so + // `response.json()` threw `Unexpected token '<', " []); const voipmsConf = conf.voipms || {}; - if (!voipmsConf.username || !voipmsConf.password || !voipmsConf.did) { - return res.status(400).json({ error: 'VoIP.ms credentials not configured. Please configure username, DID, and password in the SMS tab.' }); + if (!messagingPlugins.length && (!voipmsConf.username || !voipmsConf.password || !voipmsConf.did)) { + return res.status(400).json({ error: 'No messaging plugin is loaded and VoIP.ms credentials are not configured. Set username, DID and password in the SMS tab, or load a messaging plugin.' }); } - const testMessage = message || `SSO Manager Test SMS: This is a test message from ${conf.name}. If you received this, your VoIP.ms configuration is working correctly.`; + const testMessage = message || `SSO Manager Test SMS: This is a test message from ${conf.name}. If you received this, your SMS configuration is working correctly.`; - // VoIP.ms SMS API endpoint - const voipmsApiUrl = 'https://api.voip.ms/v1.0'; - const authHeader = Buffer.from(`${voipmsConf.username}:${voipmsConf.password}`).toString('base64'); - - const response = await fetch(`${voipmsApiUrl}/sms/send`, { - method: 'POST', - headers: { - 'Authorization': `Basic ${authHeader}`, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - did: voipmsConf.did, - to: to, - message: testMessage - }) - }); - - const result = await response.json(); - if (result.status === 'success') { - res.json({ success: true, message: `Test SMS sent to ${to}` }); - } else { - res.status(400).json({ error: `VoIP.ms API error: ${result.message || 'Unknown error'}` }); - } + await SMS.send(to, testMessage); + res.json({ success: true, message: `Test SMS sent to ${to}` }); } catch(err) { - next(err); + // The sender rejects with a useful reason (`VoIP.ms error:
+ Run this on any host and it enrolls itself. The SSO issues that host its own
+ token and public key on first connect, and the agent writes both into its
+ agent.yml — nothing to copy back and forth. One key works for as
+ many hosts as you like; each still gets its own revocable identity.
+
- The SSO issues the agent's token and records it. Tokens it did not issue are rejected, - so enroll the host first — the install command below is built from the result. + Use this when you want the agent bound to a specific Directory host from the start. + The SSO issues the token here and you copy it onto the machine yourself.