|
| 1 | +import { redirect } from '@sveltejs/kit'; |
| 2 | +import type { RequestHandler } from './$types'; |
| 3 | +import { signToken, generateId, slugify } from '$lib/server/auth'; |
| 4 | + |
| 5 | +const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token'; |
| 6 | +const GOOGLE_USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo'; |
| 7 | + |
| 8 | +export const GET: RequestHandler = async ({ url, platform, cookies }) => { |
| 9 | + const env = platform?.env; |
| 10 | + if (!env) throw new Error('Platform env not available'); |
| 11 | + |
| 12 | + const code = url.searchParams.get('code'); |
| 13 | + const state = url.searchParams.get('state'); |
| 14 | + const savedState = cookies.get('auth_state'); |
| 15 | + |
| 16 | + if (!code) { |
| 17 | + redirect(302, '/login?error=no_code'); |
| 18 | + } |
| 19 | + |
| 20 | + if (state !== savedState) { |
| 21 | + redirect(302, '/login?error=invalid_state'); |
| 22 | + } |
| 23 | + |
| 24 | + const tokenResponse = await fetch(GOOGLE_TOKEN_URL, { |
| 25 | + method: 'POST', |
| 26 | + headers: { 'Content-Type': 'application/json' }, |
| 27 | + body: JSON.stringify({ |
| 28 | + client_id: env.GOOGLE_CLIENT_ID, |
| 29 | + client_secret: env.GOOGLE_CLIENT_SECRET, |
| 30 | + code, |
| 31 | + redirect_uri: `${env.APP_URL}/api/auth/callback/google`, |
| 32 | + grant_type: 'authorization_code' |
| 33 | + }) |
| 34 | + }); |
| 35 | + |
| 36 | + const tokenData = await tokenResponse.json(); |
| 37 | + if (tokenData.error || !tokenData.access_token) { |
| 38 | + redirect(302, '/login?error=token_failed'); |
| 39 | + } |
| 40 | + |
| 41 | + const userResponse = await fetch(GOOGLE_USERINFO_URL, { |
| 42 | + headers: { Authorization: `Bearer ${tokenData.access_token}` } |
| 43 | + }); |
| 44 | + |
| 45 | + const googleUser = await userResponse.json(); |
| 46 | + if (!googleUser.id || !googleUser.email) { |
| 47 | + redirect(302, '/login?error=user_failed'); |
| 48 | + } |
| 49 | + |
| 50 | + const userId = `google_${googleUser.id}`; |
| 51 | + const reservedUsernames = ['openboot', 'admin', 'api', 'dashboard', 'install', 'login', 'logout', 'settings', 'help', 'support', 'docs', 'blog']; |
| 52 | + |
| 53 | + let username = slugify(googleUser.email.split('@')[0]); |
| 54 | + if (reservedUsernames.includes(username.toLowerCase()) || username.length < 3) { |
| 55 | + username = `user-${googleUser.id.slice(-8)}`; |
| 56 | + } |
| 57 | + |
| 58 | + const existingUser = await env.DB.prepare('SELECT username FROM users WHERE id = ?').bind(userId).first(); |
| 59 | + if (existingUser) { |
| 60 | + username = (existingUser as { username: string }).username; |
| 61 | + } |
| 62 | + |
| 63 | + await env.DB.prepare( |
| 64 | + ` |
| 65 | + INSERT INTO users (id, username, email, avatar_url, updated_at) |
| 66 | + VALUES (?, ?, ?, ?, datetime('now')) |
| 67 | + ON CONFLICT(id) DO UPDATE SET |
| 68 | + email = excluded.email, |
| 69 | + avatar_url = excluded.avatar_url, |
| 70 | + updated_at = datetime('now') |
| 71 | + ` |
| 72 | + ) |
| 73 | + .bind(userId, username, googleUser.email, googleUser.picture || '') |
| 74 | + .run(); |
| 75 | + |
| 76 | + const existingConfig = await env.DB.prepare('SELECT id FROM configs WHERE user_id = ? AND slug = ?').bind(userId, 'default').first(); |
| 77 | + |
| 78 | + if (!existingConfig) { |
| 79 | + await env.DB.prepare( |
| 80 | + ` |
| 81 | + INSERT INTO configs (id, user_id, slug, name, description, base_preset, packages) |
| 82 | + VALUES (?, ?, 'default', 'Default', 'My default configuration', 'developer', '[]') |
| 83 | + ` |
| 84 | + ) |
| 85 | + .bind(generateId(), userId) |
| 86 | + .run(); |
| 87 | + } |
| 88 | + |
| 89 | + const thirtyDays = 30 * 24 * 60 * 60; |
| 90 | + const token = await signToken({ userId, username, exp: Date.now() + thirtyDays * 1000 }, env.JWT_SECRET); |
| 91 | + |
| 92 | + cookies.set('session', token, { |
| 93 | + path: '/', |
| 94 | + httpOnly: true, |
| 95 | + secure: true, |
| 96 | + sameSite: 'lax', |
| 97 | + maxAge: thirtyDays |
| 98 | + }); |
| 99 | + |
| 100 | + const returnTo = cookies.get('auth_return_to') || '/dashboard'; |
| 101 | + cookies.delete('auth_state', { path: '/' }); |
| 102 | + cookies.delete('auth_return_to', { path: '/' }); |
| 103 | + |
| 104 | + redirect(302, returnTo); |
| 105 | +}; |
0 commit comments