Skip to content

Commit ab65f2e

Browse files
authored
Fix Freebuff CLI auth code handling (#604)
1 parent 1b6c613 commit ab65f2e

6 files changed

Lines changed: 86 additions & 20 deletions

File tree

freebuff/web/src/app/api/auth/cli/code/route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,18 @@ export async function POST(req: Request) {
5353
)
5454
}
5555

56-
const loginUrl = `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/login?auth_code=${fingerprintId}.${expiresAt}.${fingerprintHash}`
56+
// Generate login URL on the same origin that issued the auth code. This
57+
// avoids bouncing between apex/www hosts during the browser OAuth flow.
58+
const loginUrl = new URL('/login', new URL(req.url).origin)
59+
loginUrl.searchParams.set(
60+
'auth_code',
61+
`${fingerprintId}.${expiresAt}.${fingerprintHash}`,
62+
)
5763

5864
return NextResponse.json({
5965
fingerprintId,
6066
fingerprintHash,
61-
loginUrl,
67+
loginUrl: loginUrl.toString(),
6268
expiresAt,
6369
})
6470
} catch (error) {

freebuff/web/src/app/onboard/_helpers.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,24 @@ export function parseAuthCode(authCode: string): {
55
expiresAt: string
66
receivedHash: string
77
} {
8-
const [fingerprintId, expiresAt, receivedHash] = authCode.split('.')
8+
const normalizedAuthCode = authCode.trim()
9+
const hashSeparatorIndex = normalizedAuthCode.lastIndexOf('.')
10+
const expiresSeparatorIndex = normalizedAuthCode.lastIndexOf(
11+
'.',
12+
hashSeparatorIndex - 1,
13+
)
14+
15+
if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) {
16+
return { fingerprintId: '', expiresAt: '', receivedHash: '' }
17+
}
18+
19+
const fingerprintId = normalizedAuthCode.slice(0, expiresSeparatorIndex)
20+
const expiresAt = normalizedAuthCode.slice(
21+
expiresSeparatorIndex + 1,
22+
hashSeparatorIndex,
23+
)
24+
const receivedHash = normalizedAuthCode.slice(hashSeparatorIndex + 1)
25+
926
return { fingerprintId, expiresAt, receivedHash }
1027
}
1128

freebuff/web/src/app/onboard/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ const Onboard = async ({ searchParams }: PageProps) => {
100100
)
101101

102102
if (!valid) {
103+
logger.warn(
104+
{
105+
authCodeLength: authCode.length,
106+
fingerprintIdPrefix: fingerprintId.slice(0, 24),
107+
fingerprintIdLength: fingerprintId.length,
108+
expiresAt,
109+
receivedHashPrefix: receivedHash.slice(0, 12),
110+
receivedHashLength: receivedHash.length,
111+
expectedHashPrefix: fingerprintHash.slice(0, 12),
112+
expectedHashLength: fingerprintHash.length,
113+
},
114+
'Invalid Freebuff CLI auth code',
115+
)
116+
103117
return (
104118
<StatusCard
105119
title="Invalid auth code"

web/src/app/api/auth/cli/code/route.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,18 @@ export async function POST(req: Request) {
5555
)
5656
}
5757

58-
// Generate login URL without modifying the fingerprint record
59-
const loginUrl = `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/login?auth_code=${fingerprintId}.${expiresAt}.${fingerprintHash}`
58+
// Generate login URL on the same origin that issued the auth code. This
59+
// avoids bouncing between apex/www hosts during the browser OAuth flow.
60+
const loginUrl = new URL('/login', new URL(req.url).origin)
61+
loginUrl.searchParams.set(
62+
'auth_code',
63+
`${fingerprintId}.${expiresAt}.${fingerprintHash}`,
64+
)
6065

6166
return NextResponse.json({
6267
fingerprintId,
6368
fingerprintHash,
64-
loginUrl,
69+
loginUrl: loginUrl.toString(),
6570
expiresAt,
6671
})
6772
} catch (error) {

web/src/app/onboard/__tests__/helpers.test.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@ describe('onboard/_helpers', () => {
1515
})
1616

1717
test('handles auth code with dots in fingerprint id', () => {
18-
// Note: This is a potential edge case - the current implementation
19-
// only splits into 3 parts, so extra dots would be included in fingerprintId
2018
const authCode = 'fp.with.dots.1704067200000.hashvalue'
2119
const result = parseAuthCode(authCode)
2220

23-
expect(result.fingerprintId).toBe('fp')
24-
expect(result.expiresAt).toBe('with')
25-
expect(result.receivedHash).toBe('dots')
21+
expect(result.fingerprintId).toBe('fp.with.dots')
22+
expect(result.expiresAt).toBe('1704067200000')
23+
expect(result.receivedHash).toBe('hashvalue')
24+
})
25+
26+
test('trims surrounding whitespace from copied auth code', () => {
27+
const authCode = '\n fingerprint-123.1704067200000.abc123hash \t'
28+
const result = parseAuthCode(authCode)
29+
30+
expect(result.fingerprintId).toBe('fingerprint-123')
31+
expect(result.expiresAt).toBe('1704067200000')
32+
expect(result.receivedHash).toBe('abc123hash')
2633
})
2734

2835
test('handles empty string parts', () => {
@@ -38,27 +45,27 @@ describe('onboard/_helpers', () => {
3845
const authCode = 'onlyonepart'
3946
const result = parseAuthCode(authCode)
4047

41-
expect(result.fingerprintId).toBe('onlyonepart')
42-
expect(result.expiresAt).toBeUndefined()
43-
expect(result.receivedHash).toBeUndefined()
48+
expect(result.fingerprintId).toBe('')
49+
expect(result.expiresAt).toBe('')
50+
expect(result.receivedHash).toBe('')
4451
})
4552

4653
test('handles auth code with two parts', () => {
4754
const authCode = 'first.second'
4855
const result = parseAuthCode(authCode)
4956

50-
expect(result.fingerprintId).toBe('first')
51-
expect(result.expiresAt).toBe('second')
52-
expect(result.receivedHash).toBeUndefined()
57+
expect(result.fingerprintId).toBe('')
58+
expect(result.expiresAt).toBe('')
59+
expect(result.receivedHash).toBe('')
5360
})
5461

5562
test('handles empty auth code', () => {
5663
const authCode = ''
5764
const result = parseAuthCode(authCode)
5865

5966
expect(result.fingerprintId).toBe('')
60-
expect(result.expiresAt).toBeUndefined()
61-
expect(result.receivedHash).toBeUndefined()
67+
expect(result.expiresAt).toBe('')
68+
expect(result.receivedHash).toBe('')
6269
})
6370
})
6471

web/src/app/onboard/_helpers.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,24 @@ export function parseAuthCode(authCode: string): {
55
expiresAt: string
66
receivedHash: string
77
} {
8-
const [fingerprintId, expiresAt, receivedHash] = authCode.split('.')
8+
const normalizedAuthCode = authCode.trim()
9+
const hashSeparatorIndex = normalizedAuthCode.lastIndexOf('.')
10+
const expiresSeparatorIndex = normalizedAuthCode.lastIndexOf(
11+
'.',
12+
hashSeparatorIndex - 1,
13+
)
14+
15+
if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) {
16+
return { fingerprintId: '', expiresAt: '', receivedHash: '' }
17+
}
18+
19+
const fingerprintId = normalizedAuthCode.slice(0, expiresSeparatorIndex)
20+
const expiresAt = normalizedAuthCode.slice(
21+
expiresSeparatorIndex + 1,
22+
hashSeparatorIndex,
23+
)
24+
const receivedHash = normalizedAuthCode.slice(hashSeparatorIndex + 1)
25+
926
return { fingerprintId, expiresAt, receivedHash }
1027
}
1128

0 commit comments

Comments
 (0)