feat: implement Google OAuth authentication
- Add Google OAuth 2.0 login flow with passport-google-oauth20 - Create User and RefreshToken entities for session management - Implement JWT access tokens (15min) + HttpOnly refresh cookies (7 days) - Add auth endpoints: /google, /google/callback, /refresh, /me, /logout - Create LoginPage with Google sign-in button (shadcn/ui) - Add AuthGuard for protected routes with redirect preservation - Implement silent token refresh on app mount - Add UserMenu component with avatar and sign-out Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
40
backend/src/modules/auth/google.strategy.ts
Normal file
40
backend/src/modules/auth/google.strategy.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy, VerifyCallback, Profile, StrategyOptions } from 'passport-google-oauth20';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly authService: AuthService,
|
||||
) {
|
||||
const options: StrategyOptions = {
|
||||
clientID: configService.get<string>('GOOGLE_CLIENT_ID') || '',
|
||||
clientSecret: configService.get<string>('GOOGLE_CLIENT_SECRET') || '',
|
||||
callbackURL: configService.get<string>('GOOGLE_CALLBACK_URL') || '',
|
||||
scope: ['email', 'profile'],
|
||||
};
|
||||
super(options);
|
||||
}
|
||||
|
||||
async validate(
|
||||
_accessToken: string,
|
||||
_refreshToken: string,
|
||||
profile: Profile,
|
||||
done: VerifyCallback,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const user = await this.authService.validateOAuthUser({
|
||||
id: profile.id,
|
||||
emails: profile.emails as Array<{ value: string; verified: boolean }>,
|
||||
displayName: profile.displayName,
|
||||
photos: profile.photos as Array<{ value: string }>,
|
||||
});
|
||||
done(null, user);
|
||||
} catch (error) {
|
||||
done(error as Error, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user