-
Notifications
You must be signed in to change notification settings - Fork 658
Expand file tree
/
Copy pathroute.ts
More file actions
123 lines (110 loc) · 3.49 KB
/
route.ts
File metadata and controls
123 lines (110 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { randomBytes } from 'node:crypto'
import { genAuthCode } from '@codebuff/common/util/credentials'
import db from '@codebuff/internal/db'
import * as schema from '@codebuff/internal/db/schema'
import { env } from '@codebuff/internal/env'
import { and, eq, gt } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { z } from 'zod/v4'
import {
buildCliAuthCode,
getCliAuthCodeHashPrefix,
getCliAuthCodeTokenIdentifier,
} from '@/app/onboard/_helpers'
import { logger } from '@/util/logger'
import { getLoginUrlOrigin } from './_origin'
export async function POST(req: Request) {
const reqSchema = z.object({
fingerprintId: z.string(),
})
const requestBody = await req.json()
const result = reqSchema.safeParse(requestBody)
if (!result.success) {
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 })
}
const { fingerprintId } = result.data
try {
const expiresAt = Date.now() + 60 * 60 * 1000 // 1 hour
const fingerprintHash = genAuthCode(
fingerprintId,
expiresAt.toString(),
env.NEXTAUTH_SECRET,
)
// Check if this fingerprint has any active sessions
const existingSession = await db
.select({
userId: schema.session.userId,
expires: schema.session.expires,
})
.from(schema.session)
.where(
and(
eq(schema.session.fingerprint_id, fingerprintId),
gt(schema.session.expires, new Date()),
),
)
.limit(1)
if (existingSession.length > 0) {
// There's an active session - log this for monitoring
logger.info(
{
fingerprintId,
existingUserId: existingSession[0].userId,
event: 'relogin_attempt_with_active_session',
},
'Login attempt for fingerprint with active session',
)
}
const authCode = buildCliAuthCode(
fingerprintId,
expiresAt.toString(),
fingerprintHash,
)
const loginToken = randomBytes(32).toString('base64url')
await db.insert(schema.verificationToken).values({
identifier: getCliAuthCodeTokenIdentifier(loginToken),
token: authCode,
expires: new Date(expiresAt),
})
const loginUrl = new URL(
'/login',
getLoginUrlOrigin(
req,
env.NEXT_PUBLIC_CODEBUFF_APP_URL,
'https://codebuff.com',
env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod',
),
)
loginUrl.searchParams.set('auth_code', loginToken)
logger.info(
{
authCodeTokenHashPrefix: getCliAuthCodeHashPrefix(loginToken),
authCodeTokenLength: loginToken.length,
fingerprintIdPrefix: fingerprintId.slice(0, 24),
fingerprintIdLength: fingerprintId.length,
expiresAt,
loginUrlOrigin: loginUrl.origin,
requestOrigin: new URL(req.url).origin,
requestHost: req.headers.get('host'),
forwardedHost: req.headers.get('x-forwarded-host'),
forwardedProto: req.headers.get('x-forwarded-proto'),
originHeader: req.headers.get('origin'),
configuredAppUrl: env.NEXT_PUBLIC_CODEBUFF_APP_URL,
environment: env.NEXT_PUBLIC_CB_ENVIRONMENT,
},
'Issued Codebuff CLI auth code token',
)
return NextResponse.json({
fingerprintId,
fingerprintHash,
loginUrl: loginUrl.toString(),
expiresAt,
})
} catch (error) {
logger.error({ error }, 'Error generating login code')
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
)
}
}