Skip to content

Commit b2575cc

Browse files
committed
Fix auth code expiry parsing
1 parent 33a8547 commit b2575cc

4 files changed

Lines changed: 115 additions & 7 deletions

File tree

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
CardDescription,
1313
CardContent,
1414
} from '@/components/ui/card'
15+
import { isAuthCodeExpired, parseAuthCode } from '@/app/onboard/_helpers'
1516

1617
export default async function LoginPage({
1718
searchParams,
@@ -22,10 +23,9 @@ export default async function LoginPage({
2223
const authCode = resolvedSearchParams?.auth_code as string | undefined
2324

2425
if (authCode) {
25-
const [_fingerprintId, expiresAt, _receivedFingerprintHash] =
26-
authCode.split('.')
26+
const { expiresAt } = parseAuthCode(authCode)
2727

28-
if (parseInt(expiresAt) < Date.now()) {
28+
if (expiresAt && isAuthCodeExpired(expiresAt)) {
2929
return (
3030
<div className="relative min-h-screen overflow-hidden">
3131
<div className="absolute inset-0 bg-gradient-to-b from-dark-forest-green via-black/95 to-black" />
@@ -36,7 +36,9 @@ export default async function LoginPage({
3636
<div className="w-full sm:w-1/2 md:w-1/3">
3737
<Card className="border-zinc-800/80 bg-zinc-950/80 backdrop-blur-sm">
3838
<CardHeader>
39-
<CardTitle className="text-white">Auth code expired</CardTitle>
39+
<CardTitle className="text-white">
40+
Auth code expired
41+
</CardTitle>
4042
<CardDescription>
4143
Please try starting Freebuff in your terminal again.
4244
</CardDescription>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { genAuthCode } from '@codebuff/common/util/credentials'
2+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
3+
4+
import { parseAuthCode, validateAuthCode, isAuthCodeExpired } from '../_helpers'
5+
6+
describe('freebuff onboard/_helpers', () => {
7+
describe('parseAuthCode', () => {
8+
test('parses valid auth code with three parts', () => {
9+
const authCode = 'fingerprint-123.1704067200000.abc123hash'
10+
const result = parseAuthCode(authCode)
11+
12+
expect(result.fingerprintId).toBe('fingerprint-123')
13+
expect(result.expiresAt).toBe('1704067200000')
14+
expect(result.receivedHash).toBe('abc123hash')
15+
})
16+
17+
test('handles auth code with dots in fingerprint id', () => {
18+
const authCode = 'fp.with.dots.1704067200000.hashvalue'
19+
const result = parseAuthCode(authCode)
20+
21+
expect(result.fingerprintId).toBe('fp.with.dots')
22+
expect(result.expiresAt).toBe('1704067200000')
23+
expect(result.receivedHash).toBe('hashvalue')
24+
})
25+
26+
test('handles auth code missing separator before expiresAt', () => {
27+
const authCode =
28+
'fingerprint-1231704067200000.abc123hashabc123hashabc123hash'
29+
const result = parseAuthCode(authCode)
30+
31+
expect(result.fingerprintId).toBe('')
32+
expect(result.expiresAt).toBe('')
33+
expect(result.receivedHash).toBe('')
34+
})
35+
})
36+
37+
describe('validateAuthCode', () => {
38+
const testSecret = 'test-secret-key'
39+
const testFingerprintId = 'fp-abc123'
40+
const testExpiresAt = '1704067200000'
41+
42+
test('returns valid=true when hash matches', () => {
43+
const expectedHash = genAuthCode(
44+
testFingerprintId,
45+
testExpiresAt,
46+
testSecret,
47+
)
48+
const result = validateAuthCode(
49+
expectedHash,
50+
testFingerprintId,
51+
testExpiresAt,
52+
testSecret,
53+
)
54+
55+
expect(result.valid).toBe(true)
56+
expect(result.expectedHash).toBe(expectedHash)
57+
})
58+
59+
test('returns valid=false when hash does not match', () => {
60+
const result = validateAuthCode(
61+
'wrong-hash-value',
62+
testFingerprintId,
63+
testExpiresAt,
64+
testSecret,
65+
)
66+
67+
expect(result.valid).toBe(false)
68+
})
69+
})
70+
71+
describe('isAuthCodeExpired', () => {
72+
let originalDateNow: typeof Date.now
73+
74+
beforeEach(() => {
75+
originalDateNow = Date.now
76+
})
77+
78+
afterEach(() => {
79+
Date.now = originalDateNow
80+
})
81+
82+
test('returns true when expiresAt is in the past', () => {
83+
Date.now = () => 1704067200000
84+
expect(isAuthCodeExpired('1704067199999')).toBe(true)
85+
})
86+
87+
test('returns false when expiresAt is in the future', () => {
88+
Date.now = () => 1704067200000
89+
expect(isAuthCodeExpired('1704067200001')).toBe(false)
90+
})
91+
92+
test('treats malformed timestamps as expired', () => {
93+
expect(isAuthCodeExpired('not-a-number')).toBe(true)
94+
})
95+
})
96+
})

web/src/app/login/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
CardDescription,
1111
CardContent,
1212
} from '@/components/ui/card'
13+
import { isAuthCodeExpired, parseAuthCode } from '@/app/onboard/_helpers'
1314

1415
// Server component that handles the auth code expiration check
1516
export default async function LoginPage({
@@ -21,11 +22,10 @@ export default async function LoginPage({
2122
const authCode = resolvedSearchParams?.auth_code as string | undefined
2223

2324
if (authCode) {
24-
const [_fingerprintId, expiresAt, _receivedfingerprintHash] =
25-
authCode.split('.')
25+
const { expiresAt } = parseAuthCode(authCode)
2626

2727
// Check for token expiration on the server side
28-
if (parseInt(expiresAt) < Date.now()) {
28+
if (expiresAt && isAuthCodeExpired(expiresAt)) {
2929
return (
3030
<Card>
3131
<CardHeader>

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ describe('onboard/_helpers', () => {
3232
expect(result.receivedHash).toBe('abc123hash')
3333
})
3434

35+
test('handles auth code missing separator before expiresAt', () => {
36+
const authCode =
37+
'fingerprint-1231704067200000.abc123hashabc123hashabc123hash'
38+
const result = parseAuthCode(authCode)
39+
40+
expect(result.fingerprintId).toBe('')
41+
expect(result.expiresAt).toBe('')
42+
expect(result.receivedHash).toBe('')
43+
})
44+
3545
test('handles empty string parts', () => {
3646
const authCode = '..emptyparts'
3747
const result = parseAuthCode(authCode)

0 commit comments

Comments
 (0)