// Dashboard HTTP surface for the email-code login flow. // // Routes (all POST, all behind the dashboard auth gate): // /windsurf-login/probe → login-method classification, no credential // /windsurf-login/email/start → {flowId, sent} — token stays server-side // /windsurf-login/email/complete → same shape as /windsurf-login // // Zero network: the windsurf-login transport seam is injected, so the routes // exercise the real handlers with fake upstream replies. No live email is sent. import { afterEach, beforeEach, describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { config } from '../src/config.js'; import { configureBindHost, removeAccount } from '../src/auth.js'; import { handleDashboardApi } from '../src/dashboard/api.js'; import { _resetRuntimeConfigForTests } from '../src/runtime-config.js'; import { __setLoginTransportForTests, _resetEmailLockoutForTests, _resetEmailFlowsForTests, } from '../src/dashboard/windsurf-login.js'; const PASSWORD_HEADER = { 'x-dashboard-password': 'dash-secret' }; const EMAIL = 'routes-email-code@example.test'; const CODE = '654321'; const VERIFY_TOKEN = 'route-verify-token-throwaway'; const SESSION_TOKEN = 'devin-session-token$ROUTEEMAILCODE'; const originalDashboardPassword = config.dashboardPassword; const originalApiKey = config.apiKey; let createdAccountIds = []; function fakeRes() { return { statusCode: 0, headers: {}, body: '', setHeader(k, v) { this.headers[k] = v; }, writeHead(status, headers) { this.statusCode = status; this.headers = headers || {}; }, end(chunk) { this.body += chunk ? String(chunk) : ''; }, json() { return this.body ? JSON.parse(this.body) : null; }, }; } function req(headers = PASSWORD_HEADER) { return { url: '/dashboard/api/windsurf-login', headers, socket: { remoteAddress: '127.0.0.1' } }; } async function call(subpath, body, headers = PASSWORD_HEADER) { const res = fakeRes(); await handleDashboardApi('POST', subpath, body, req(headers), res); return { status: res.statusCode, body: res.json() }; } function makeTransport(routes) { const seen = []; const fn = async (url, opts) => { seen.push(url); for (const [matcher, reply] of routes) { if (url.includes(matcher)) { if (reply instanceof Error) throw reply; if (opts.raw) { const buf = Buffer.isBuffer(reply.data) ? reply.data : Buffer.from(JSON.stringify(reply.data)); return { status: reply.status, data: buf }; } return { status: reply.status, data: reply.data }; } } throw new Error(`unexpected request to ${url}`); }; fn.seen = seen; return fn; } const CONNECTIONS_OAUTH_ONLY = ['windsurf.com/_devin-auth/connections', { status: 200, data: { connections: [{ id: 'email', type: 'email', enabled: true }], auth_method: { method: 'auth1', has_password: false }, }, }]; const EMAIL_START_OK = ['windsurf.com/_devin-auth/email/start', { status: 200, data: { email_verification_token: VERIFY_TOKEN }, }]; const EMAIL_COMPLETE_OK = ['windsurf.com/_devin-auth/email/complete', { status: 200, data: { token: 'auth1-route-throwaway' }, }]; const POSTAUTH_OK = ['WindsurfPostAuth', { status: 200, data: { sessionToken: SESSION_TOKEN, accountId: 'account-route' }, }]; beforeEach(() => { _resetRuntimeConfigForTests(); _resetEmailLockoutForTests(); _resetEmailFlowsForTests(); config.dashboardPassword = 'dash-secret'; config.apiKey = ''; configureBindHost('0.0.0.0'); }); afterEach(() => { __setLoginTransportForTests(null); for (const id of createdAccountIds) removeAccount(id); createdAccountIds = []; _resetEmailLockoutForTests(); _resetEmailFlowsForTests(); _resetRuntimeConfigForTests(); config.dashboardPassword = originalDashboardPassword; config.apiKey = originalApiKey; configureBindHost('0.0.0.0'); }); describe('email-code routes require dashboard auth', () => { for (const subpath of ['/windsurf-login/probe', '/windsurf-login/email/start', '/windsurf-login/email/complete']) { it(`${subpath} is 401 without the dashboard password`, async () => { const t = makeTransport([]); __setLoginTransportForTests(t); const { status } = await call(subpath, { email: EMAIL, flowId: 'x', code: CODE }, {}); assert.equal(status, 401); assert.deepEqual(t.seen, [], 'an unauthenticated call must not reach upstream'); }); } }); describe('POST /windsurf-login/probe', () => { it('reports hasPassword:false + emailCode for the OAuth-only account', async () => { __setLoginTransportForTests(makeTransport([CONNECTIONS_OAUTH_ONLY])); const { status, body } = await call('/windsurf-login/probe', { email: EMAIL }); assert.equal(status, 200); assert.equal(body.ok, true); assert.equal(body.hasPassword, false); assert.equal(body.emailCode, true); assert.equal(body.userExists, true); assert.equal(body.method, 'auth1'); assert.equal(body.source, 'connections'); assert.equal('email_verification_token' in body, false); }); it('requires an email', async () => { const { status, body } = await call('/windsurf-login/probe', {}); assert.equal(status, 400); assert.equal(body.error, 'ERR_EMAIL_REQUIRED'); }); it('refuses to probe an email that is under the login lockout', async () => { const t = makeTransport([ ['windsurf.com/_devin-auth/connections', { status: 200, data: { auth_method: { method: 'auth1', has_password: true } } }], ['windsurf.com/_devin-auth/password/login', { status: 401, data: { detail: 'Invalid email or password' } }], ]); __setLoginTransportForTests(t); // 3 credential failures trip the default threshold of 3. for (let i = 0; i < 3; i++) { const r = await call('/windsurf-login', { email: EMAIL, password: 'wrong-guess' }); assert.equal(r.status, 400); } const { status, body } = await call('/windsurf-login/probe', { email: EMAIL }); assert.equal(status, 400); assert.equal(body.error, 'ERR_EMAIL_LOCKED'); assert.ok(body.retryAfterMs > 0); }); }); describe('POST /windsurf-login/email/start', () => { it('returns only the opaque flowId — never the verification token', async () => { __setLoginTransportForTests(makeTransport([EMAIL_START_OK])); const { status, body } = await call('/windsurf-login/email/start', { email: EMAIL }); assert.equal(status, 200); assert.equal(body.sent, true); assert.match(body.flowId, /^[0-9a-f]{32}$/); assert.equal(JSON.stringify(body).includes(VERIFY_TOKEN), false); }); it('surfaces the per-email resend cooldown without tripping the IP ban overlay', async () => { __setLoginTransportForTests(makeTransport([EMAIL_START_OK])); await call('/windsurf-login/email/start', { email: EMAIL }); const { status, body } = await call('/windsurf-login/email/start', { email: EMAIL }); assert.equal(status, 400, '400 (not 429) keeps the UI from rendering an IP-wide ban'); assert.equal(body.error, 'ERR_EMAIL_CODE_COOLDOWN'); assert.ok(body.retryAfterMs > 0); }); it('a failed send still arms the cooldown', async () => { const t = makeTransport([['windsurf.com/_devin-auth/email/start', { status: 400, data: { detail: 'sender unavailable' } }]]); __setLoginTransportForTests(t); const first = await call('/windsurf-login/email/start', { email: EMAIL }); assert.equal(first.status, 400); assert.equal(first.body.error, 'ERR_EMAIL_START_FAILED'); const second = await call('/windsurf-login/email/start', { email: EMAIL }); assert.equal(second.status, 400); assert.equal(second.body.error, 'ERR_EMAIL_CODE_COOLDOWN'); assert.equal(t.seen.length, 1, 'the failing upstream must not be called again inside the window'); }); }); describe('POST /windsurf-login/email/complete', () => { it('logs in and returns the /windsurf-login response shape', async () => { __setLoginTransportForTests(makeTransport([EMAIL_START_OK, EMAIL_COMPLETE_OK, POSTAUTH_OK])); const start = await call('/windsurf-login/email/start', { email: EMAIL }); const { status, body } = await call('/windsurf-login/email/complete', { flowId: start.body.flowId, code: CODE, autoAdd: false, }); assert.equal(status, 200); assert.equal(body.success, true); assert.equal(body.apiKey, SESSION_TOKEN, 'autoAdd:false returns the raw key, like the password route'); assert.equal(body.email, EMAIL); assert.equal(body.method, 'email_code'); assert.equal(body.account, null); }); it('auto-adds the account to the pool by default', async () => { __setLoginTransportForTests(makeTransport([EMAIL_START_OK, EMAIL_COMPLETE_OK, POSTAUTH_OK])); const start = await call('/windsurf-login/email/start', { email: EMAIL }); const { status, body } = await call('/windsurf-login/email/complete', { flowId: start.body.flowId, code: CODE, }); assert.equal(status, 200); assert.equal(body.success, true); assert.ok(body.account?.id, 'the account must be persisted'); assert.notEqual(body.apiKey_masked, SESSION_TOKEN, 'the raw key must not be echoed when it was added to the pool'); assert.match(body.apiKey_masked, /\.\.\./, 'the pool path returns a masked key, like the password route'); createdAccountIds.push(body.account.id); }); it('rejects a used flow with 409 and an expired flow with 410', async () => { __setLoginTransportForTests(makeTransport([EMAIL_START_OK, EMAIL_COMPLETE_OK, POSTAUTH_OK])); const start = await call('/windsurf-login/email/start', { email: EMAIL }); const first = await call('/windsurf-login/email/complete', { flowId: start.body.flowId, code: CODE, autoAdd: false, }); assert.equal(first.status, 200); const replay = await call('/windsurf-login/email/complete', { flowId: start.body.flowId, code: CODE, autoAdd: false, }); assert.equal(replay.status, 409); assert.equal(replay.body.error, 'ERR_EMAIL_FLOW_USED'); const unknown = await call('/windsurf-login/email/complete', { flowId: '0123456789abcdef0123456789abcdef', code: CODE, autoAdd: false, }); assert.equal(unknown.status, 404); assert.equal(unknown.body.error, 'ERR_EMAIL_FLOW_NOT_FOUND'); }); it('rejects a wrong code with the remaining attempts and no account added', async () => { __setLoginTransportForTests(makeTransport([ EMAIL_START_OK, ['windsurf.com/_devin-auth/email/complete', { status: 400, data: { detail: 'Invalid verification code' } }], ])); const start = await call('/windsurf-login/email/start', { email: EMAIL }); const { status, body } = await call('/windsurf-login/email/complete', { flowId: start.body.flowId, code: '000000', }); assert.equal(status, 400); assert.equal(body.error, 'ERR_EMAIL_CODE_REJECTED'); assert.equal(body.isAuthFail, true); assert.equal(body.attemptsRemaining, 4); assert.equal(body.account, undefined); }); it('requires both flowId and code', async () => { const { status, body } = await call('/windsurf-login/email/complete', { flowId: 'x' }); assert.equal(status, 400); assert.equal(body.error, 'ERR_EMAIL_FLOW_CODE_REQUIRED'); }); });