Skip to content

Commit 4a632e1

Browse files
committed
Use opaque CLI auth tokens
1 parent ca95525 commit 4a632e1

6 files changed

Lines changed: 68 additions & 10 deletions

File tree

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { randomBytes } from 'node:crypto'
2+
13
import { genAuthCode } from '@codebuff/common/util/credentials'
24
import db from '@codebuff/internal/db'
35
import * as schema from '@codebuff/internal/db/schema'
@@ -55,6 +57,15 @@ export async function POST(req: Request) {
5557
)
5658
}
5759

60+
const authCode = `${fingerprintId}.${expiresAt}.${fingerprintHash}`
61+
const loginToken = randomBytes(32).toString('base64url')
62+
63+
await db.insert(schema.verificationToken).values({
64+
identifier: `cli-login:${loginToken}`,
65+
token: authCode,
66+
expires: new Date(expiresAt),
67+
})
68+
5869
const loginUrl = new URL(
5970
'/login',
6071
getLoginUrlOrigin(
@@ -64,10 +75,7 @@ export async function POST(req: Request) {
6475
env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod',
6576
),
6677
)
67-
loginUrl.searchParams.set(
68-
'auth_code',
69-
`${fingerprintId}.${expiresAt}.${fingerprintHash}`,
70-
)
78+
loginUrl.searchParams.set('auth_code', loginToken)
7179

7280
return NextResponse.json({
7381
fingerprintId,

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ export async function hasCliSessionForAuthHash(
3232
return existing.length > 0
3333
}
3434

35+
export async function getCliAuthCodeForToken(
36+
authCodeToken: string,
37+
): Promise<string | null> {
38+
const existing = await db
39+
.select({ authCode: schema.verificationToken.token })
40+
.from(schema.verificationToken)
41+
.where(
42+
and(
43+
eq(schema.verificationToken.identifier, `cli-login:${authCodeToken}`),
44+
gt(schema.verificationToken.expires, new Date()),
45+
),
46+
)
47+
.limit(1)
48+
49+
return existing[0]?.authCode ?? null
50+
}
51+
3552
export async function checkFingerprintConflict(
3653
fingerprintId: string,
3754
userId: string,

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getServerSession } from 'next-auth'
77
import {
88
checkFingerprintConflict,
99
createCliSession,
10+
getCliAuthCodeForToken,
1011
getSessionTokenFromCookies,
1112
hasCliSessionForAuthHash,
1213
} from './_db'
@@ -91,7 +92,9 @@ const Onboard = async ({ searchParams }: PageProps) => {
9192
)
9293
}
9394

94-
const { fingerprintId, expiresAt, receivedHash } = parseAuthCode(authCode)
95+
const resolvedAuthCode = (await getCliAuthCodeForToken(authCode)) ?? authCode
96+
const { fingerprintId, expiresAt, receivedHash } =
97+
parseAuthCode(resolvedAuthCode)
9598
const { valid, expectedHash: fingerprintHash } = validateAuthCode(
9699
receivedHash,
97100
fingerprintId,
@@ -103,6 +106,8 @@ const Onboard = async ({ searchParams }: PageProps) => {
103106
logger.warn(
104107
{
105108
authCodeLength: authCode.length,
109+
resolvedAuthCode: resolvedAuthCode !== authCode,
110+
resolvedAuthCodeLength: resolvedAuthCode.length,
106111
dotCount: authCode.match(/\./g)?.length ?? 0,
107112
hyphenCount: authCode.match(/-/g)?.length ?? 0,
108113
fingerprintIdPrefix: fingerprintId.slice(0, 24),

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { randomBytes } from 'node:crypto'
2+
13
import { genAuthCode } from '@codebuff/common/util/credentials'
24
import db from '@codebuff/internal/db'
35
import * as schema from '@codebuff/internal/db/schema'
@@ -57,6 +59,15 @@ export async function POST(req: Request) {
5759
)
5860
}
5961

62+
const authCode = `${fingerprintId}.${expiresAt}.${fingerprintHash}`
63+
const loginToken = randomBytes(32).toString('base64url')
64+
65+
await db.insert(schema.verificationToken).values({
66+
identifier: `cli-login:${loginToken}`,
67+
token: authCode,
68+
expires: new Date(expiresAt),
69+
})
70+
6071
const loginUrl = new URL(
6172
'/login',
6273
getLoginUrlOrigin(
@@ -66,10 +77,7 @@ export async function POST(req: Request) {
6677
env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod',
6778
),
6879
)
69-
loginUrl.searchParams.set(
70-
'auth_code',
71-
`${fingerprintId}.${expiresAt}.${fingerprintHash}`,
72-
)
80+
loginUrl.searchParams.set('auth_code', loginToken)
7381

7482
return NextResponse.json({
7583
fingerprintId,

web/src/app/onboard/_db.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ export async function hasCliSessionForAuthHash(
3232
return existing.length > 0
3333
}
3434

35+
export async function getCliAuthCodeForToken(
36+
authCodeToken: string,
37+
): Promise<string | null> {
38+
const existing = await db
39+
.select({ authCode: schema.verificationToken.token })
40+
.from(schema.verificationToken)
41+
.where(
42+
and(
43+
eq(schema.verificationToken.identifier, `cli-login:${authCodeToken}`),
44+
gt(schema.verificationToken.expires, new Date()),
45+
),
46+
)
47+
.limit(1)
48+
49+
return existing[0]?.authCode ?? null
50+
}
51+
3552
export async function checkFingerprintConflict(
3653
fingerprintId: string,
3754
userId: string,

web/src/app/onboard/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getServerSession } from 'next-auth'
77
import {
88
checkFingerprintConflict,
99
createCliSession,
10+
getCliAuthCodeForToken,
1011
getSessionTokenFromCookies,
1112
hasCliSessionForAuthHash,
1213
} from './_db'
@@ -48,7 +49,9 @@ const Onboard = async ({ searchParams }: PageProps) => {
4849
)
4950
}
5051

51-
const { fingerprintId, expiresAt, receivedHash } = parseAuthCode(authCode)
52+
const resolvedAuthCode = (await getCliAuthCodeForToken(authCode)) ?? authCode
53+
const { fingerprintId, expiresAt, receivedHash } =
54+
parseAuthCode(resolvedAuthCode)
5255
const { valid, expectedHash: fingerprintHash } = validateAuthCode(
5356
receivedHash,
5457
fingerprintId,

0 commit comments

Comments
 (0)