mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 21:31:14 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
174
lib/services/auth/oauthService.ts
Normal file
174
lib/services/auth/oauthService.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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<void> {
|
||||
// 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<AuthResponse>(
|
||||
`/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<void> {
|
||||
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<void> {
|
||||
await apiClient.post(`/api/v1/auth/oauth/${provider}/unlink`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of linked OAuth providers
|
||||
*/
|
||||
export async function getLinkedProviders(): Promise<OAuthProvider[]> {
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user