Skip to content

Commit 772c0ff

Browse files
authored
auth (#27)
* github/vercel integration * fixes * format * fixes * format * update readme * 2.0.0 * fix types * fix lint * fix build * format * fix issue 1 * fix issue 2 * cleanup * fixes * fix github token * add upgrade instructions * update text * fix access
1 parent 648a29e commit 772c0ff

File tree

86 files changed

+6063
-1346
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+6063
-1346
lines changed

README.md

Lines changed: 317 additions & 29 deletions
Large diffs are not rendered by default.

app/api/api-keys/route.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { getSessionFromReq } from '@/lib/session/server'
3+
import { db } from '@/lib/db/client'
4+
import { keys } from '@/lib/db/schema'
5+
import { eq, and } from 'drizzle-orm'
6+
import { nanoid } from 'nanoid'
7+
import { encrypt, decrypt } from '@/lib/crypto'
8+
9+
type Provider = 'openai' | 'gemini' | 'cursor' | 'anthropic' | 'aigateway'
10+
11+
export async function GET(req: NextRequest) {
12+
try {
13+
const session = await getSessionFromReq(req)
14+
15+
if (!session?.user?.id) {
16+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
17+
}
18+
19+
const userKeys = await db
20+
.select({
21+
provider: keys.provider,
22+
createdAt: keys.createdAt,
23+
})
24+
.from(keys)
25+
.where(eq(keys.userId, session.user.id))
26+
27+
return NextResponse.json({
28+
success: true,
29+
apiKeys: userKeys,
30+
})
31+
} catch (error) {
32+
console.error('Error fetching API keys:', error)
33+
return NextResponse.json({ error: 'Failed to fetch API keys' }, { status: 500 })
34+
}
35+
}
36+
37+
export async function POST(req: NextRequest) {
38+
try {
39+
const session = await getSessionFromReq(req)
40+
41+
if (!session?.user?.id) {
42+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
43+
}
44+
45+
const body = await req.json()
46+
const { provider, apiKey } = body as { provider: Provider; apiKey: string }
47+
48+
if (!provider || !apiKey) {
49+
return NextResponse.json({ error: 'Provider and API key are required' }, { status: 400 })
50+
}
51+
52+
if (!['openai', 'gemini', 'cursor', 'anthropic', 'aigateway'].includes(provider)) {
53+
return NextResponse.json({ error: 'Invalid provider' }, { status: 400 })
54+
}
55+
56+
// Check if key already exists
57+
const existing = await db
58+
.select()
59+
.from(keys)
60+
.where(and(eq(keys.userId, session.user.id), eq(keys.provider, provider)))
61+
.limit(1)
62+
63+
const encryptedKey = encrypt(apiKey)
64+
65+
if (existing.length > 0) {
66+
// Update existing
67+
await db
68+
.update(keys)
69+
.set({
70+
value: encryptedKey,
71+
updatedAt: new Date(),
72+
})
73+
.where(and(eq(keys.userId, session.user.id), eq(keys.provider, provider)))
74+
} else {
75+
// Insert new
76+
await db.insert(keys).values({
77+
id: nanoid(),
78+
userId: session.user.id,
79+
provider,
80+
value: encryptedKey,
81+
})
82+
}
83+
84+
return NextResponse.json({ success: true })
85+
} catch (error) {
86+
console.error('Error saving API key:', error)
87+
return NextResponse.json({ error: 'Failed to save API key' }, { status: 500 })
88+
}
89+
}
90+
91+
export async function DELETE(req: NextRequest) {
92+
try {
93+
const session = await getSessionFromReq(req)
94+
95+
if (!session?.user?.id) {
96+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
97+
}
98+
99+
const { searchParams } = new URL(req.url)
100+
const provider = searchParams.get('provider') as Provider
101+
102+
if (!provider) {
103+
return NextResponse.json({ error: 'Provider is required' }, { status: 400 })
104+
}
105+
106+
await db.delete(keys).where(and(eq(keys.userId, session.user.id), eq(keys.provider, provider)))
107+
108+
return NextResponse.json({ success: true })
109+
} catch (error) {
110+
console.error('Error deleting API key:', error)
111+
return NextResponse.json({ error: 'Failed to delete API key' }, { status: 500 })
112+
}
113+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { type NextRequest } from 'next/server'
2+
import { OAuth2Client, type OAuth2Tokens } from 'arctic'
3+
import { createSession, saveSession } from '@/lib/session/create'
4+
import { cookies } from 'next/headers'
5+
6+
export async function GET(req: NextRequest): Promise<Response> {
7+
const code = req.nextUrl.searchParams.get('code')
8+
const state = req.nextUrl.searchParams.get('state')
9+
const cookieStore = await cookies()
10+
const storedState = cookieStore.get(`vercel_oauth_state`)?.value ?? null
11+
const storedVerifier = cookieStore.get(`vercel_oauth_code_verifier`)?.value ?? null
12+
const storedRedirectTo = cookieStore.get(`vercel_oauth_redirect_to`)?.value ?? null
13+
14+
if (
15+
code === null ||
16+
state === null ||
17+
storedState !== state ||
18+
storedRedirectTo === null ||
19+
storedVerifier === null
20+
) {
21+
return new Response(null, {
22+
status: 400,
23+
})
24+
}
25+
26+
const client = new OAuth2Client(
27+
process.env.VERCEL_CLIENT_ID ?? '',
28+
process.env.VERCEL_CLIENT_SECRET ?? '',
29+
`${req.nextUrl.origin}/api/auth/callback/vercel`,
30+
)
31+
32+
let tokens: OAuth2Tokens
33+
34+
try {
35+
tokens = await client.validateAuthorizationCode('https://vercel.com/api/login/oauth/token', code, storedVerifier)
36+
} catch (error) {
37+
console.error('Failed to validate authorization code:', error)
38+
return new Response(null, {
39+
status: 400,
40+
})
41+
}
42+
43+
const response = new Response(null, {
44+
status: 302,
45+
headers: {
46+
Location: storedRedirectTo,
47+
},
48+
})
49+
50+
const session = await createSession({
51+
accessToken: tokens.accessToken(),
52+
expiresAt: tokens.accessTokenExpiresAt().getTime(),
53+
refreshToken: tokens.hasRefreshToken() ? tokens.refreshToken() : undefined,
54+
})
55+
56+
if (!session) {
57+
console.error('[Vercel Callback] Failed to create session')
58+
return new Response('Failed to create session', { status: 500 })
59+
}
60+
61+
// Note: Vercel tokens are already stored in users table by upsertUser() in createSession()
62+
63+
await saveSession(response, session)
64+
65+
cookieStore.delete(`vercel_oauth_state`)
66+
cookieStore.delete(`vercel_oauth_code_verifier`)
67+
cookieStore.delete(`vercel_oauth_redirect_to`)
68+
69+
return response
70+
}

0 commit comments

Comments
 (0)