Files
web-app-launcher/src/routes/auth/oauth/callback/+server.ts
T
alexei.dolgolyov cba160ecb8 fix: address all code review findings
- Extract shared permission logic into boardPermissions.ts utility
- Fix DnD drag revert: add dirty flag to prevent  overwrite
- Wrap OAuth group sync in Prisma transaction (N+1 fix)
- Add empty widgetIds validation in widget reorder API
- Add invalidateAll() after guest toggle PATCH
- Replace console.error with user-visible error banners
- Extract WidgetCreationForm component (DraggableSection was 448 lines)
- Remove unused boardId prop from DraggableSection
- Always include OAuth state parameter + validate in callback
- Clean up copyLink timer on component destroy
- Add type-specific widget config validation in addWidget action
2026-03-25 00:03:32 +03:00

95 lines
3.1 KiB
TypeScript

import { redirect, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types.js';
import * as oauthService from '$lib/server/services/oauthService.js';
import * as userService from '$lib/server/services/userService.js';
import * as authService from '$lib/server/services/authService.js';
const COOKIE_BASE = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/'
};
export const GET: RequestHandler = async ({ url, cookies }) => {
try {
// Check for error response from the provider
const oauthError = url.searchParams.get('error');
if (oauthError) {
const description = url.searchParams.get('error_description') || oauthError;
throw new Error(`OAuth provider returned an error: ${description}`);
}
// Ensure we have an authorization code
const code = url.searchParams.get('code');
if (!code) {
throw new Error('No authorization code received from OAuth provider');
}
// Retrieve the code_verifier and state from cookies
const codeVerifier = cookies.get('oauth_code_verifier');
if (!codeVerifier) {
throw new Error('OAuth session expired. Please try logging in again.');
}
const expectedState = cookies.get('oauth_state');
if (!expectedState) {
throw new Error('OAuth session expired. Please try logging in again.');
}
// Validate the state parameter matches to prevent CSRF
const returnedState = url.searchParams.get('state');
if (returnedState !== expectedState) {
throw new Error('OAuth state mismatch. Possible CSRF attack. Please try logging in again.');
}
// Clear the OAuth cookies
cookies.delete('oauth_code_verifier', { path: '/' });
cookies.delete('oauth_state', { path: '/' });
// Exchange the authorization code for tokens and get user info
const userInfo = await oauthService.handleCallback(url, codeVerifier, expectedState);
// Find or create local user from OAuth info
const user = await userService.findOrCreateByOAuth({
email: userInfo.email,
displayName: userInfo.name || userInfo.preferred_username || userInfo.email.split('@')[0],
avatarUrl: userInfo.picture,
groups: userInfo.groups ? [...userInfo.groups] : undefined
});
// Issue local JWT tokens (same as local auth flow)
const accessToken = authService.signAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = authService.generateRefreshToken();
await authService.saveRefreshToken(user.id, refreshToken);
// Set session cookies
cookies.set('access_token', accessToken, {
...COOKIE_BASE,
maxAge: 900 // 15 minutes
});
cookies.set('refresh_token', refreshToken, {
...COOKIE_BASE,
maxAge: 604800 // 7 days
});
cookies.set('refresh_user_id', user.id, {
...COOKIE_BASE,
maxAge: 604800 // 7 days
});
throw redirect(302, '/');
} catch (err) {
// Re-throw redirects
if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 302) {
throw err;
}
const message = err instanceof Error ? err.message : 'OAuth authentication failed';
throw error(500, message);
}
};