Files
rxminder/services/oauth.ts

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;