From 049e836e7d451801fcf91f6fd354e24540524c2f Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 8 May 2026 15:11:41 -0700 Subject: [PATCH 1/3] Fix legacy CLI auth code parsing --- .../web/src/app/onboard/__tests__/helpers.test.ts | 10 ++++++++++ freebuff/web/src/app/onboard/_helpers.ts | 11 +++++++++++ freebuff/web/src/app/onboard/page.tsx | 2 ++ web/src/app/onboard/__tests__/helpers.test.ts | 10 ++++++++++ web/src/app/onboard/_helpers.ts | 11 +++++++++++ 5 files changed, 44 insertions(+) diff --git a/freebuff/web/src/app/onboard/__tests__/helpers.test.ts b/freebuff/web/src/app/onboard/__tests__/helpers.test.ts index 4b4596a8b..4d9d0eab9 100644 --- a/freebuff/web/src/app/onboard/__tests__/helpers.test.ts +++ b/freebuff/web/src/app/onboard/__tests__/helpers.test.ts @@ -23,6 +23,16 @@ describe('freebuff onboard/_helpers', () => { expect(result.receivedHash).toBe('hashvalue') }) + test('parses legacy hyphen-delimited auth code', () => { + const receivedHash = 'a'.repeat(64) + const authCode = `1234567890abcdef1234567890abcdef-1704067200000-${receivedHash}` + const result = parseAuthCode(authCode) + + expect(result.fingerprintId).toBe('1234567890abcdef1234567890abcdef') + expect(result.expiresAt).toBe('1704067200000') + expect(result.receivedHash).toBe(receivedHash) + }) + test('handles auth code missing separator before expiresAt', () => { const authCode = 'fingerprint-1231704067200000.abc123hashabc123hashabc123hash' diff --git a/freebuff/web/src/app/onboard/_helpers.ts b/freebuff/web/src/app/onboard/_helpers.ts index d502d0d20..850a3eaec 100644 --- a/freebuff/web/src/app/onboard/_helpers.ts +++ b/freebuff/web/src/app/onboard/_helpers.ts @@ -13,6 +13,17 @@ export function parseAuthCode(authCode: string): { ) if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) { + const legacyMatch = normalizedAuthCode.match( + /^(?.+)-(?\d+)-(?[a-f0-9]{64})$/i, + ) + if (legacyMatch?.groups) { + return { + fingerprintId: legacyMatch.groups.fingerprintId, + expiresAt: legacyMatch.groups.expiresAt, + receivedHash: legacyMatch.groups.receivedHash, + } + } + return { fingerprintId: '', expiresAt: '', receivedHash: '' } } diff --git a/freebuff/web/src/app/onboard/page.tsx b/freebuff/web/src/app/onboard/page.tsx index 287b761f4..180758a23 100644 --- a/freebuff/web/src/app/onboard/page.tsx +++ b/freebuff/web/src/app/onboard/page.tsx @@ -103,6 +103,8 @@ const Onboard = async ({ searchParams }: PageProps) => { logger.warn( { authCodeLength: authCode.length, + dotCount: authCode.match(/\./g)?.length ?? 0, + hyphenCount: authCode.match(/-/g)?.length ?? 0, fingerprintIdPrefix: fingerprintId.slice(0, 24), fingerprintIdLength: fingerprintId.length, expiresAt, diff --git a/web/src/app/onboard/__tests__/helpers.test.ts b/web/src/app/onboard/__tests__/helpers.test.ts index 6c5c433e5..2d10f2447 100644 --- a/web/src/app/onboard/__tests__/helpers.test.ts +++ b/web/src/app/onboard/__tests__/helpers.test.ts @@ -32,6 +32,16 @@ describe('onboard/_helpers', () => { expect(result.receivedHash).toBe('abc123hash') }) + test('parses legacy hyphen-delimited auth code', () => { + const receivedHash = 'a'.repeat(64) + const authCode = `1234567890abcdef1234567890abcdef-1704067200000-${receivedHash}` + const result = parseAuthCode(authCode) + + expect(result.fingerprintId).toBe('1234567890abcdef1234567890abcdef') + expect(result.expiresAt).toBe('1704067200000') + expect(result.receivedHash).toBe(receivedHash) + }) + test('handles auth code missing separator before expiresAt', () => { const authCode = 'fingerprint-1231704067200000.abc123hashabc123hashabc123hash' diff --git a/web/src/app/onboard/_helpers.ts b/web/src/app/onboard/_helpers.ts index d502d0d20..850a3eaec 100644 --- a/web/src/app/onboard/_helpers.ts +++ b/web/src/app/onboard/_helpers.ts @@ -13,6 +13,17 @@ export function parseAuthCode(authCode: string): { ) if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) { + const legacyMatch = normalizedAuthCode.match( + /^(?.+)-(?\d+)-(?[a-f0-9]{64})$/i, + ) + if (legacyMatch?.groups) { + return { + fingerprintId: legacyMatch.groups.fingerprintId, + expiresAt: legacyMatch.groups.expiresAt, + receivedHash: legacyMatch.groups.receivedHash, + } + } + return { fingerprintId: '', expiresAt: '', receivedHash: '' } } From baf5f0b77d68030383300a4d5786afa2d838ce67 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 8 May 2026 15:20:46 -0700 Subject: [PATCH 2/3] Stabilize Gemini thinker session test --- .../completions/__tests__/completions.test.ts | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts b/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts index 12604ea60..59317e23c 100644 --- a/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts +++ b/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts @@ -1144,47 +1144,54 @@ describe('/api/v1/chat/completions POST endpoint', () => { expect(body.error).toBe('session_model_mismatch') }) - it('requires an active session check for the Gemini thinker subagent', async () => { - const checkFreeModeRateLimitForTest = mock((userId: string) => { - expect(userId).toBe('user-new-free-gemini') - return { limited: false as const } - }) + it( + 'requires an active session check for the Gemini thinker subagent', + async () => { + const checkFreeModeRateLimitForTest = mock((userId: string) => { + expect(userId).toBe('user-new-free-gemini') + return { limited: false as const } + }) - const response = await postChatCompletions({ - req: new NextRequest('http://localhost:3000/api/v1/chat/completions', { - method: 'POST', - headers: allowedFreeModeHeaders('test-api-key-new-free-gemini'), - body: JSON.stringify({ - model: FREEBUFF_GEMINI_PRO_MODEL_ID, - stream: false, - codebuff_metadata: { - run_id: 'run-gemini-thinker-child', - client_id: 'test-client-id-123', - cost_mode: 'free', - freebuff_instance_id: 'inst-123', + const response = await postChatCompletions({ + req: new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: allowedFreeModeHeaders('test-api-key-new-free-gemini'), + body: JSON.stringify({ + model: FREEBUFF_GEMINI_PRO_MODEL_ID, + stream: false, + codebuff_metadata: { + run_id: 'run-gemini-thinker-child', + client_id: 'test-client-id-123', + cost_mode: 'free', + freebuff_instance_id: 'inst-123', + }, + }), }, - }), - }), - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: async (params) => { - expect(params.requireActiveSession).toBe(true) - expect(params.requestedModel).toBe(FREEBUFF_GEMINI_PRO_MODEL_ID) - expect(params.claimedInstanceId).toBe('inst-123') - return { ok: true, reason: 'active', remainingMs: 60_000 } - }, - checkFreeModeRateLimit: checkFreeModeRateLimitForTest, - }) + ), + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: async (params) => { + expect(params.requireActiveSession).toBe(true) + expect(params.requestedModel).toBe(FREEBUFF_GEMINI_PRO_MODEL_ID) + expect(params.claimedInstanceId).toBe('inst-123') + return { ok: true, reason: 'active', remainingMs: 60_000 } + }, + checkFreeModeRateLimit: checkFreeModeRateLimitForTest, + }) - expect(response.status).toBe(200) - expect(checkFreeModeRateLimitForTest).toHaveBeenCalledTimes(1) - }) + expect(response.status).toBe(200) + expect(checkFreeModeRateLimitForTest).toHaveBeenCalledTimes(1) + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) it( 'counts child Gemini thinker requests toward the free-mode request limit', From ca95525689392847ca5a621ae934adb90306a892 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 8 May 2026 15:29:15 -0700 Subject: [PATCH 3/3] Stabilize chat completions fetch-path tests --- .../completions/__tests__/completions.test.ts | 342 +++++++++--------- 1 file changed, 181 insertions(+), 161 deletions(-) diff --git a/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts b/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts index 59317e23c..84f189390 100644 --- a/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts +++ b/web/src/app/api/v1/chat/completions/__tests__/completions.test.ts @@ -520,108 +520,120 @@ describe('/api/v1/chat/completions POST endpoint', () => { expect(body.message).not.toContain(nextQuotaReset) }) - it('lets a new account with no paid relationship through for non-free mode', async () => { - const req = new NextRequest( - 'http://localhost:3000/api/v1/chat/completions', - { - method: 'POST', - headers: { Authorization: 'Bearer test-api-key-new-free' }, - body: JSON.stringify({ - model: 'test/test-model', - stream: false, - codebuff_metadata: { - run_id: 'run-123', - client_id: 'test-client-id-123', - }, - }), - }, - ) + it( + 'lets a new account with no paid relationship through for non-free mode', + async () => { + const req = new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: { Authorization: 'Bearer test-api-key-new-free' }, + body: JSON.stringify({ + model: 'test/test-model', + stream: false, + codebuff_metadata: { + run_id: 'run-123', + client_id: 'test-client-id-123', + }, + }), + }, + ) - const response = await postChatCompletions({ - req, - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: mockCheckSessionAdmissibleAllow, - }) + const response = await postChatCompletions({ + req, + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: mockCheckSessionAdmissibleAllow, + }) - expect(response.status).toBe(200) - }) + expect(response.status).toBe(200) + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) - it('lets a BYOK free-tier new account through the paid-plan gate', async () => { - const req = new NextRequest( - 'http://localhost:3000/api/v1/chat/completions', - { - method: 'POST', - headers: { - Authorization: 'Bearer test-api-key-new-free', - 'x-openrouter-api-key': 'sk-or-byok-test', - }, - body: JSON.stringify({ - model: 'test/test-model', - stream: false, - codebuff_metadata: { - run_id: 'run-123', - client_id: 'test-client-id-123', + it( + 'lets a BYOK free-tier new account through the paid-plan gate', + async () => { + const req = new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: { + Authorization: 'Bearer test-api-key-new-free', + 'x-openrouter-api-key': 'sk-or-byok-test', }, - }), - }, - ) + body: JSON.stringify({ + model: 'test/test-model', + stream: false, + codebuff_metadata: { + run_id: 'run-123', + client_id: 'test-client-id-123', + }, + }), + }, + ) - const response = await postChatCompletions({ - req, - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: mockCheckSessionAdmissibleAllow, - }) + const response = await postChatCompletions({ + req, + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: mockCheckSessionAdmissibleAllow, + }) - expect(response.status).toBe(200) - }) + expect(response.status).toBe(200) + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) - it('lets a freebuff/free-mode request through even for a brand-new unpaid account', async () => { - const req = new NextRequest( - 'http://localhost:3000/api/v1/chat/completions', - { - method: 'POST', - headers: allowedFreeModeHeaders('test-api-key-new-free'), - body: JSON.stringify({ - model: 'minimax/minimax-m2.7', - stream: false, - codebuff_metadata: { - run_id: 'run-free', - client_id: 'test-client-id-123', - cost_mode: 'free', - }, - }), - }, - ) + it( + 'lets a freebuff/free-mode request through even for a brand-new unpaid account', + async () => { + const req = new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: allowedFreeModeHeaders('test-api-key-new-free'), + body: JSON.stringify({ + model: 'minimax/minimax-m2.7', + stream: false, + codebuff_metadata: { + run_id: 'run-free', + client_id: 'test-client-id-123', + cost_mode: 'free', + }, + }), + }, + ) - const response = await postChatCompletions({ - req, - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: mockCheckSessionAdmissibleAllow, - }) + const response = await postChatCompletions({ + req, + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: mockCheckSessionAdmissibleAllow, + }) - expect(response.status).toBe(200) - }) + expect(response.status).toBe(200) + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) it('rejects free-mode requests when location is unknown', async () => { const req = new NextRequest( @@ -1033,39 +1045,43 @@ describe('/api/v1/chat/completions POST endpoint', () => { expect(body.error).toBe('free_mode_invalid_agent_model') }) - it('allows browser-use as a free-mode subagent under a freebuff root', async () => { - const req = new NextRequest( - 'http://localhost:3000/api/v1/chat/completions', - { - method: 'POST', - headers: allowedFreeModeHeaders('test-api-key-new-free-gemini'), - body: JSON.stringify({ - model: 'google/gemini-3.1-flash-lite-preview', - stream: false, - codebuff_metadata: { - run_id: 'run-browser-use-child', - client_id: 'test-client-id-123', - cost_mode: 'free', - }, - }), - }, - ) + it( + 'allows browser-use as a free-mode subagent under a freebuff root', + async () => { + const req = new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: allowedFreeModeHeaders('test-api-key-new-free-gemini'), + body: JSON.stringify({ + model: 'google/gemini-3.1-flash-lite-preview', + stream: false, + codebuff_metadata: { + run_id: 'run-browser-use-child', + client_id: 'test-client-id-123', + cost_mode: 'free', + }, + }), + }, + ) - const response = await postChatCompletions({ - req, - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: mockCheckSessionAdmissibleAllow, - }) + const response = await postChatCompletions({ + req, + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: mockCheckSessionAdmissibleAllow, + }) - expect(response.status).toBe(200) - }) + expect(response.status).toBe(200) + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) it('rejects standalone free-mode reviewer runs even when the model is allowlisted', async () => { const req = new NextRequest( @@ -1402,45 +1418,49 @@ describe('/api/v1/chat/completions POST endpoint', () => { }) describe('Successful responses', () => { - it('returns stream with correct headers', async () => { - const req = new NextRequest( - 'http://localhost:3000/api/v1/chat/completions', - { - method: 'POST', - headers: { Authorization: 'Bearer test-api-key-123' }, - body: JSON.stringify({ - stream: true, - codebuff_metadata: { - run_id: 'run-123', - client_id: 'test-client-id-123', - client_request_id: 'test-client-session-id-123', - }, - }), - }, - ) + it( + 'returns stream with correct headers', + async () => { + const req = new NextRequest( + 'http://localhost:3000/api/v1/chat/completions', + { + method: 'POST', + headers: { Authorization: 'Bearer test-api-key-123' }, + body: JSON.stringify({ + stream: true, + codebuff_metadata: { + run_id: 'run-123', + client_id: 'test-client-id-123', + client_request_id: 'test-client-session-id-123', + }, + }), + }, + ) - const response = await postChatCompletions({ - req, - getUserInfoFromApiKey: mockGetUserInfoFromApiKey, - logger: mockLogger, - trackEvent: mockTrackEvent, - getUserUsageData: mockGetUserUsageData, - getAgentRunFromId: mockGetAgentRunFromId, - fetch: mockFetch, - insertMessageBigquery: mockInsertMessageBigquery, - loggerWithContext: mockLoggerWithContext, - checkSessionAdmissible: mockCheckSessionAdmissibleAllow, - }) + const response = await postChatCompletions({ + req, + getUserInfoFromApiKey: mockGetUserInfoFromApiKey, + logger: mockLogger, + trackEvent: mockTrackEvent, + getUserUsageData: mockGetUserUsageData, + getAgentRunFromId: mockGetAgentRunFromId, + fetch: mockFetch, + insertMessageBigquery: mockInsertMessageBigquery, + loggerWithContext: mockLoggerWithContext, + checkSessionAdmissible: mockCheckSessionAdmissibleAllow, + }) - if (response.status !== 200) { - const errorBody = await response.json() - console.log('Error response:', errorBody) - } - expect(response.status).toBe(200) - expect(response.headers.get('Content-Type')).toBe('text/event-stream') - expect(response.headers.get('Cache-Control')).toBe('no-cache') - expect(response.headers.get('Connection')).toBe('keep-alive') - }) + if (response.status !== 200) { + const errorBody = await response.json() + console.log('Error response:', errorBody) + } + expect(response.status).toBe(200) + expect(response.headers.get('Content-Type')).toBe('text/event-stream') + expect(response.headers.get('Cache-Control')).toBe('no-cache') + expect(response.headers.get('Connection')).toBe('keep-alive') + }, + FETCH_PATH_TEST_TIMEOUT_MS, + ) it( 'returns JSON response for non-streaming requests',