Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions freebuff/web/src/app/api/auth/cli/code/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ export async function POST(req: Request) {
)
}

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

return NextResponse.json({
fingerprintId,
fingerprintHash,
loginUrl,
loginUrl: loginUrl.toString(),
expiresAt,
})
} catch (error) {
Expand Down
19 changes: 18 additions & 1 deletion freebuff/web/src/app/onboard/_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ export function parseAuthCode(authCode: string): {
expiresAt: string
receivedHash: string
} {
const [fingerprintId, expiresAt, receivedHash] = authCode.split('.')
const normalizedAuthCode = authCode.trim()
const hashSeparatorIndex = normalizedAuthCode.lastIndexOf('.')
const expiresSeparatorIndex = normalizedAuthCode.lastIndexOf(
'.',
hashSeparatorIndex - 1,
)

if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) {
return { fingerprintId: '', expiresAt: '', receivedHash: '' }
}

const fingerprintId = normalizedAuthCode.slice(0, expiresSeparatorIndex)
const expiresAt = normalizedAuthCode.slice(
expiresSeparatorIndex + 1,
hashSeparatorIndex,
)
const receivedHash = normalizedAuthCode.slice(hashSeparatorIndex + 1)

return { fingerprintId, expiresAt, receivedHash }
}

Expand Down
14 changes: 14 additions & 0 deletions freebuff/web/src/app/onboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ const Onboard = async ({ searchParams }: PageProps) => {
)

if (!valid) {
logger.warn(
{
authCodeLength: authCode.length,
fingerprintIdPrefix: fingerprintId.slice(0, 24),
fingerprintIdLength: fingerprintId.length,
expiresAt,
receivedHashPrefix: receivedHash.slice(0, 12),
receivedHashLength: receivedHash.length,
expectedHashPrefix: fingerprintHash.slice(0, 12),
expectedHashLength: fingerprintHash.length,
},
'Invalid Freebuff CLI auth code',
)

return (
<StatusCard
title="Invalid auth code"
Expand Down
11 changes: 8 additions & 3 deletions web/src/app/api/auth/cli/code/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,18 @@ export async function POST(req: Request) {
)
}

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

return NextResponse.json({
fingerprintId,
fingerprintHash,
loginUrl,
loginUrl: loginUrl.toString(),
expiresAt,
})
} catch (error) {
Expand Down
33 changes: 20 additions & 13 deletions web/src/app/onboard/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@ describe('onboard/_helpers', () => {
})

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

expect(result.fingerprintId).toBe('fp')
expect(result.expiresAt).toBe('with')
expect(result.receivedHash).toBe('dots')
expect(result.fingerprintId).toBe('fp.with.dots')
expect(result.expiresAt).toBe('1704067200000')
expect(result.receivedHash).toBe('hashvalue')
})

test('trims surrounding whitespace from copied auth code', () => {
const authCode = '\n fingerprint-123.1704067200000.abc123hash \t'
const result = parseAuthCode(authCode)

expect(result.fingerprintId).toBe('fingerprint-123')
expect(result.expiresAt).toBe('1704067200000')
expect(result.receivedHash).toBe('abc123hash')
})

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

expect(result.fingerprintId).toBe('onlyonepart')
expect(result.expiresAt).toBeUndefined()
expect(result.receivedHash).toBeUndefined()
expect(result.fingerprintId).toBe('')
expect(result.expiresAt).toBe('')
expect(result.receivedHash).toBe('')
})

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

expect(result.fingerprintId).toBe('first')
expect(result.expiresAt).toBe('second')
expect(result.receivedHash).toBeUndefined()
expect(result.fingerprintId).toBe('')
expect(result.expiresAt).toBe('')
expect(result.receivedHash).toBe('')
})

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

expect(result.fingerprintId).toBe('')
expect(result.expiresAt).toBeUndefined()
expect(result.receivedHash).toBeUndefined()
expect(result.expiresAt).toBe('')
expect(result.receivedHash).toBe('')
})
})

Expand Down
19 changes: 18 additions & 1 deletion web/src/app/onboard/_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ export function parseAuthCode(authCode: string): {
expiresAt: string
receivedHash: string
} {
const [fingerprintId, expiresAt, receivedHash] = authCode.split('.')
const normalizedAuthCode = authCode.trim()
const hashSeparatorIndex = normalizedAuthCode.lastIndexOf('.')
const expiresSeparatorIndex = normalizedAuthCode.lastIndexOf(
'.',
hashSeparatorIndex - 1,
)

if (hashSeparatorIndex === -1 || expiresSeparatorIndex === -1) {
return { fingerprintId: '', expiresAt: '', receivedHash: '' }
}

const fingerprintId = normalizedAuthCode.slice(0, expiresSeparatorIndex)
const expiresAt = normalizedAuthCode.slice(
expiresSeparatorIndex + 1,
hashSeparatorIndex,
)
const receivedHash = normalizedAuthCode.slice(hashSeparatorIndex + 1)

return { fingerprintId, expiresAt, receivedHash }
}

Expand Down
Loading