diff --git a/apps/daemon/src/routes/vela.ts b/apps/daemon/src/routes/vela.ts index 8bf5cd0852..a0777c3956 100644 --- a/apps/daemon/src/routes/vela.ts +++ b/apps/daemon/src/routes/vela.ts @@ -8,6 +8,7 @@ import { createHash, randomUUID } from 'node:crypto'; import dns from 'node:dns'; import http from 'node:http'; import https from 'node:https'; +import { isDeepStrictEqual } from 'node:util'; import zlib from 'node:zlib'; import { @@ -1110,7 +1111,7 @@ export function registerVelaRoutes(app: Express, deps: RegisterVelaRoutesDeps): if (typeof req.body?.target !== 'string') { res.status(400).json({error:'invalid_target'}); return; } const revision = readVelaCredentialRevision(env, configuredEnv); const result = await createVelaWebIntent({...context,consoleOrigin,target:req.body.target}); - if (readVelaCredentialRevision(env, configuredEnv) !== revision) { res.status(409).json({error:'account_changed'}); return; } + if (!isDeepStrictEqual(readVelaCredentialRevision(env, configuredEnv), revision)) { res.status(409).json({error:'account_changed'}); return; } res.json(result); } catch (error) { const code = error instanceof Error ? error.message : 'amr_web_intent_unavailable'; diff --git a/apps/daemon/tests/integrations/vela.routes.test.ts b/apps/daemon/tests/integrations/vela.routes.test.ts index 3cbdac527f..09acbf2c14 100644 --- a/apps/daemon/tests/integrations/vela.routes.test.ts +++ b/apps/daemon/tests/integrations/vela.routes.test.ts @@ -290,6 +290,32 @@ afterEach(async () => { } }); +describe('POST /api/amr/web-intent', () => { + it.each([false, true])('checks credential content across the upstream request (changed=%s)', async (changed) => { + const previousOrigin = process.env.OD_VELA_WEB_URL; + process.env.OD_VELA_WEB_URL = 'http://localhost:5173'; + const upstream = await startWalletApi((req, res) => { + expect(req.url).toBe('/api/v1/auth/intents'); + expect(req.headers.authorization).toBe('Bearer ck-seeded-key'); + if (changed) seedLogin('local', { apiUrl: upstream.url, controlKey: 'ck-replaced-key' }); + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ intentId: 'intent-local' })); + }); + seedLogin('local', { apiUrl: upstream.url }); + try { + const result = await postJson(`${baseUrl}/api/amr/web-intent`, { target: '/dashboard' }); + expect(result.status).toBe(changed ? 409 : 200); + expect(result.body).toEqual(changed + ? { error: 'account_changed' } + : { url: 'http://localhost:5173/login?intentId=intent-local' }); + } finally { + if (previousOrigin === undefined) delete process.env.OD_VELA_WEB_URL; + else process.env.OD_VELA_WEB_URL = previousOrigin; + await upstream.close(); + } + }); +}); + describe('GET /api/integrations/vela/wallet', () => { it('returns signed_out without reading an upstream wallet API', async () => { const walletApi = await startWalletApi((_req, res) => {