176 lines
5.4 KiB
TypeScript
176 lines
5.4 KiB
TypeScript
import { authService } from './auth/auth.service';
|
|
import {
|
|
NavigationService,
|
|
navigationService,
|
|
} from './navigation/navigation.interface';
|
|
|
|
import { getAppConfig, getOAuthConfig } from '../config/unified.config';
|
|
|
|
// Mock OAuth configuration
|
|
const { google, github } = getOAuthConfig();
|
|
const GOOGLE_CLIENT_ID = google?.clientId || 'mock_google_client_id';
|
|
const GITHUB_CLIENT_ID = github?.clientId || 'mock_github_client_id';
|
|
|
|
// Mock OAuth endpoints
|
|
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
|
|
|
|
// Mock OAuth scopes
|
|
const GOOGLE_SCOPES = 'openid email profile';
|
|
const GITHUB_SCOPES = 'user:email';
|
|
|
|
// Mock redirect URI
|
|
const APP_BASE_URL = getAppConfig().baseUrl;
|
|
const REDIRECT_URI = `${APP_BASE_URL.replace(/\/$/, '')}/auth/callback`;
|
|
|
|
// Mock OAuth state generation
|
|
const generateState = () => crypto.randomUUID();
|
|
|
|
/**
|
|
* OAuth Service Class
|
|
*
|
|
* Handles OAuth authentication flows with dependency injection for navigation,
|
|
* making it more testable and maintainable.
|
|
*/
|
|
export class OAuthService {
|
|
constructor(private navigation: NavigationService = navigationService) {}
|
|
|
|
/**
|
|
* Initiates Google OAuth authentication flow
|
|
*/
|
|
googleAuth(): void {
|
|
const state = generateState();
|
|
const url = new URL(GOOGLE_AUTH_URL);
|
|
url.searchParams.append('client_id', GOOGLE_CLIENT_ID);
|
|
url.searchParams.append('response_type', 'code');
|
|
url.searchParams.append('scope', GOOGLE_SCOPES);
|
|
url.searchParams.append('redirect_uri', REDIRECT_URI);
|
|
url.searchParams.append('state', state);
|
|
|
|
// Store state for CSRF protection
|
|
localStorage.setItem('oauth_state', state);
|
|
|
|
// Redirect to Google's auth endpoint
|
|
this.navigation.redirectTo(url.toString());
|
|
}
|
|
|
|
/**
|
|
* Initiates GitHub OAuth authentication flow
|
|
*/
|
|
githubAuth(): void {
|
|
const state = generateState();
|
|
const url = new URL(GITHUB_AUTH_URL);
|
|
url.searchParams.append('client_id', GITHUB_CLIENT_ID);
|
|
url.searchParams.append('response_type', 'code');
|
|
url.searchParams.append('scope', GITHUB_SCOPES);
|
|
url.searchParams.append('redirect_uri', REDIRECT_URI);
|
|
url.searchParams.append('state', state);
|
|
|
|
// Store state for CSRF protection
|
|
localStorage.setItem('oauth_state', state);
|
|
|
|
// Redirect to GitHub's auth endpoint
|
|
this.navigation.redirectTo(url.toString());
|
|
}
|
|
|
|
/**
|
|
* Handles Google OAuth callback
|
|
*/
|
|
async handleGoogleCallback() {
|
|
const params = this.navigation.getSearchParams();
|
|
const code = params.get('code');
|
|
const state = params.get('state');
|
|
const storedState = localStorage.getItem('oauth_state');
|
|
|
|
// Verify state to prevent CSRF attacks
|
|
if (state !== storedState) {
|
|
throw new Error('Invalid OAuth state');
|
|
}
|
|
|
|
// Clear stored state
|
|
localStorage.removeItem('oauth_state');
|
|
|
|
// Exchange code for token
|
|
const accessToken = await this.mockExchangeCodeForToken('google', code);
|
|
|
|
// Get user info
|
|
const userInfo = await this.mockGetUserInfo('google', accessToken);
|
|
|
|
// Register or login the user
|
|
return authService.loginWithOAuth('google', {
|
|
email: userInfo.email,
|
|
username: userInfo.name,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handles GitHub OAuth callback
|
|
*/
|
|
async handleGithubCallback() {
|
|
const params = this.navigation.getSearchParams();
|
|
const code = params.get('code');
|
|
const state = params.get('state');
|
|
const storedState = localStorage.getItem('oauth_state');
|
|
|
|
// Verify state to prevent CSRF attacks
|
|
if (state !== storedState) {
|
|
throw new Error('Invalid OAuth state');
|
|
}
|
|
|
|
// Clear stored state
|
|
localStorage.removeItem('oauth_state');
|
|
|
|
// Exchange code for token
|
|
const accessToken = await this.mockExchangeCodeForToken('github', code);
|
|
|
|
// Get user info
|
|
const userInfo = await this.mockGetUserInfo('github', accessToken);
|
|
|
|
// Register or login the user
|
|
return authService.loginWithOAuth('github', {
|
|
email: userInfo.email,
|
|
username: userInfo.name,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mock token exchange
|
|
* In a real implementation, this would make a POST request to the token endpoint
|
|
*/
|
|
private async mockExchangeCodeForToken(
|
|
provider: 'google' | 'github',
|
|
_code: string | null
|
|
): Promise<string> {
|
|
// For this mock, we'll just return a mock access token
|
|
return `mock_${provider}_access_token_${crypto.randomUUID()}`;
|
|
}
|
|
|
|
/**
|
|
* Mock user info retrieval
|
|
* In a real implementation, this would make a GET request to the user info endpoint
|
|
*/
|
|
private async mockGetUserInfo(
|
|
provider: 'google' | 'github',
|
|
_accessToken: string
|
|
): Promise<{ email: string; name: string }> {
|
|
// For this mock, we'll return mock user info
|
|
return {
|
|
email: `mock_${provider}_user_${crypto.randomUUID()}@example.com`,
|
|
name: `Mock ${provider.charAt(0).toUpperCase() + provider.slice(1)} User`,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Default instance for production use
|
|
const oauthService = new OAuthService();
|
|
|
|
// Export both the class and convenience functions for backward compatibility
|
|
export const googleAuth = () => oauthService.googleAuth();
|
|
export const githubAuth = () => oauthService.githubAuth();
|
|
export const handleGoogleCallback = () => oauthService.handleGoogleCallback();
|
|
export const handleGithubCallback = () => oauthService.handleGithubCallback();
|
|
|
|
// Export the service instance for direct use
|
|
export { oauthService };
|
|
export default oauthService;
|