import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy, type Profile, type VerifyCallback } from 'passport-google-oauth20'; import { type OAuthService, type OAuthUserProfile } from '../services/oauth.service'; @Injectable() export class GoogleOAuthStrategy extends PassportStrategy(Strategy, 'google') { constructor(private readonly oauthService: OAuthService) { const clientID = process.env['GOOGLE_CLIENT_ID']; const clientSecret = process.env['GOOGLE_CLIENT_SECRET']; const callbackURL = process.env['GOOGLE_CALLBACK_URL'] ?? '/auth/google/callback'; if (!clientID || !clientSecret) { // Use dummy values so the app can start without Google OAuth configured. // The Google login route will be non-functional until real credentials are set. super({ clientID: 'NOT_CONFIGURED', clientSecret: 'NOT_CONFIGURED', callbackURL, scope: ['email', 'profile'], }); return; } super({ clientID, clientSecret, callbackURL, scope: ['email', 'profile'], }); } async validate( accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback, ): Promise { const email = profile.emails?.[0]?.value; const photo = profile.photos?.[0]?.value; const fullName = profile.displayName || [profile.name?.givenName, profile.name?.familyName].filter(Boolean).join(' ') || 'Google User'; const oauthProfile: OAuthUserProfile = { provider: 'GOOGLE', providerUserId: profile.id, email, fullName, avatarUrl: photo, accessToken, refreshToken, rawProfile: profile._json as Record, }; try { const tokens = await this.oauthService.authenticateOAuth(oauthProfile); done(null, tokens); } catch (err) { done(err as Error, undefined); } } }