import { OAuthProvider, OAuthTokens, OAuthProfile, AuthorizationUrlParams, TokenExchangeParams, ProviderConfig } from "./OAuthProvider"; import { OAuthProviderBase } from "./OAuthProviderBase"; /** * Google OAuth 2.0 provider implementation * * Implements OAuth flow for Google: * - Authorization URL: https://accounts.google.com/o/oauth2/v2/auth * - Token exchange: POST https://oauth2.googleapis.com/token * - User profile: GET https://www.googleapis.com/oauth2/v2/userinfo * * Default scopes: ['openid', 'email', 'profile'] */ export class GoogleProvider extends OAuthProviderBase implements OAuthProvider { public readonly name = "google"; private static readonly AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth"; private static readonly TOKEN_URL = "https://oauth2.googleapis.com/token"; private static readonly USER_PROFILE_URL = "https://www.googleapis.com/oauth2/v2/userinfo"; private static readonly DEFAULT_SCOPES = ["openid", "email", "profile"]; constructor(config: ProviderConfig) { super(config); } /** * Generate Google authorization URL */ getAuthorizationUrl(params: AuthorizationUrlParams): string { const scopes = params.scope.length > 0 ? params.scope : GoogleProvider.DEFAULT_SCOPES; const queryParams = { client_id: this.config.clientId, redirect_uri: params.redirectUri, state: params.state, scope: scopes, response_type: "code", access_type: "offline", }; return `${GoogleProvider.AUTHORIZATION_URL}?${this.buildQueryString(queryParams)}`; } /** * Exchange authorization code for access token */ async exchangeCodeForToken(params: TokenExchangeParams): Promise { const body = this.buildQueryString({ client_id: this.config.clientId, client_secret: this.config.clientSecret, code: params.code, redirect_uri: params.redirectUri, grant_type: "authorization_code", }); const response = await this.makeHttpRequest(GoogleProvider.TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body, }); return { accessToken: response.access_token, refreshToken: response.refresh_token, scope: response.scope, expiresIn: response.expires_in, }; } /** * Fetch user profile from Google */ async getUserProfile(accessToken: string): Promise { const response = await this.makeHttpRequest(GoogleProvider.USER_PROFILE_URL, { method: "GET", headers: { Authorization: `Bearer ${accessToken}`, }, }); return { id: response.id, email: response.email, name: response.name, avatarUrl: response.picture, raw: response, }; } }