/** * OAuth Service * * Handles OAuth authentication flow with Google and Discord providers. * Works with django-allauth backend for seamless OAuth integration. */ import { apiClient } from '@/lib/api/client'; import { AuthResponse } from '@/lib/types/auth'; import { tokenStorage } from './tokenStorage'; export type OAuthProvider = 'google' | 'discord'; /** * OAuth state management for CSRF protection */ const OAUTH_STATE_KEY = 'oauth_state'; const OAUTH_REDIRECT_KEY = 'oauth_redirect'; /** * Generate random state for OAuth CSRF protection */ function generateState(): string { const array = new Uint8Array(32); crypto.getRandomValues(array); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); } /** * Store OAuth state in sessionStorage */ function storeState(state: string, redirectUrl?: string): void { if (typeof window === 'undefined') return; sessionStorage.setItem(OAUTH_STATE_KEY, state); if (redirectUrl) { sessionStorage.setItem(OAUTH_REDIRECT_KEY, redirectUrl); } } /** * Retrieve and validate OAuth state */ function validateState(state: string): boolean { if (typeof window === 'undefined') return false; const storedState = sessionStorage.getItem(OAUTH_STATE_KEY); sessionStorage.removeItem(OAUTH_STATE_KEY); return storedState === state; } /** * Get stored redirect URL */ function getRedirectUrl(): string | null { if (typeof window === 'undefined') return null; const redirectUrl = sessionStorage.getItem(OAUTH_REDIRECT_KEY); sessionStorage.removeItem(OAUTH_REDIRECT_KEY); return redirectUrl; } /** * Initiate OAuth flow for a provider * * @param provider - OAuth provider ('google' or 'discord') * @param redirectUrl - Optional URL to redirect to after successful OAuth */ export async function initiateOAuth( provider: OAuthProvider, redirectUrl?: string ): Promise { // Generate state for CSRF protection const state = generateState(); storeState(state, redirectUrl); // Build OAuth initiation URL const callbackUrl = `${window.location.origin}/auth/oauth/callback`; const params = new URLSearchParams({ state, redirect_uri: callbackUrl, }); // Redirect to OAuth provider via Django backend const oauthUrl = `${process.env.NEXT_PUBLIC_DJANGO_API_URL}/api/v1/auth/oauth/${provider}/?${params}`; window.location.href = oauthUrl; } /** * Handle OAuth callback after provider authentication * * @param provider - OAuth provider * @param code - Authorization code from provider * @param state - State parameter for CSRF validation * @returns Auth response with JWT tokens and user info */ export async function handleOAuthCallback( provider: OAuthProvider, code: string, state: string ): Promise<{ authResponse: AuthResponse; redirectUrl: string | null }> { // Validate state to prevent CSRF attacks if (!validateState(state)) { throw new Error('Invalid OAuth state - possible CSRF attack'); } // Exchange code for JWT tokens const response = await apiClient.post( `/api/v1/auth/oauth/${provider}/callback`, { code, state } ); // Store tokens tokenStorage.setAccessToken(response.data.access); tokenStorage.setRefreshToken(response.data.refresh); // Get redirect URL if stored const redirectUrl = getRedirectUrl(); return { authResponse: response.data, redirectUrl, }; } /** * Link OAuth provider to existing account * * @param provider - OAuth provider to link */ export async function linkOAuthProvider(provider: OAuthProvider): Promise { const state = generateState(); storeState(state); const callbackUrl = `${window.location.origin}/auth/oauth/link/callback`; const params = new URLSearchParams({ state, redirect_uri: callbackUrl, link: 'true', }); const oauthUrl = `${process.env.NEXT_PUBLIC_DJANGO_API_URL}/api/v1/auth/oauth/${provider}/?${params}`; window.location.href = oauthUrl; } /** * Unlink OAuth provider from account * * @param provider - OAuth provider to unlink */ export async function unlinkOAuthProvider(provider: OAuthProvider): Promise { await apiClient.post(`/api/v1/auth/oauth/${provider}/unlink`); } /** * Get list of linked OAuth providers */ export async function getLinkedProviders(): Promise { const response = await apiClient.get<{ providers: OAuthProvider[] }>( '/api/v1/auth/oauth/linked' ); return response.data.providers; } // Export all functions as a service object export const oauthService = { initiateOAuth, handleOAuthCallback, linkOAuthProvider, unlinkOAuthProvider, getLinkedProviders, };