Skip to content

Commit d4a94e6

Browse files
feat(backend): implement Google OAuth strategy and endpoints
1 parent d609f56 commit d4a94e6

4 files changed

Lines changed: 67 additions & 3 deletions

File tree

apps/backend/src/auth/auth.controller.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import { Body, Controller, Get, Logger, Post, Req, Res } from '@nestjs/common';
1+
import { Body, Controller, Get, Logger, Post, Req, Res, UseGuards } from '@nestjs/common';
22
import { Throttle } from '@nestjs/throttler';
33
import { Request, Response } from 'express';
44
import { AuthService } from './auth.service';
55
import { RegisterDto } from './dto/register.dto';
66
import { LoginDto } from './dto/login.dto';
77
import { GoogleLoginDto } from './dto/google-login.dto';
8+
import { GoogleOAuthGuard } from './guards/google-oauth.guard';
9+
import { AppConfigService } from '../config/app-config.service';
810

911
@Controller('auth')
1012
export class AuthController {
1113
private readonly logger = new Logger(AuthController.name);
1214

13-
constructor(private readonly authService: AuthService) {}
15+
constructor(
16+
private readonly authService: AuthService,
17+
private readonly config: AppConfigService,
18+
) {}
1419

1520
@Get('csrf-token')
1621
getCsrfToken(@Req() req: Request & { csrfToken?: () => string }) {
@@ -45,6 +50,28 @@ export class AuthController {
4550
return payload;
4651
}
4752

53+
@Get('google')
54+
@UseGuards(GoogleOAuthGuard)
55+
async googleAuth() {
56+
// Redirects to Google
57+
}
58+
59+
@Get('google/callback')
60+
@UseGuards(GoogleOAuthGuard)
61+
async googleAuthRedirect(@Req() req: Request & { user: any }, @Res() res: Response) {
62+
this.logger.log(`GET /auth/google/callback email=${req.user.email}`);
63+
const payload = await this.authService.googleLogin({
64+
email: req.user.email,
65+
name: req.user.name,
66+
avatarUrl: req.user.avatarUrl,
67+
});
68+
this.authService.setRefreshTokenCookie(res, payload.refreshToken);
69+
70+
// Redirect back to frontend dashboard
71+
const frontendUrl = this.config.corsOrigins[0]; // Usually the web frontend is first
72+
res.redirect(`${frontendUrl}/dashboard`);
73+
}
74+
4875
@Post('refresh')
4976
async refresh(@Req() req: Request & { cookies?: Record<string, string> }, @Res({ passthrough: true }) res: Response) {
5077
const token = req.cookies?.refresh_token;

apps/backend/src/auth/auth.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PrismaModule } from '../common/prisma.module';
77
import { AuthController } from './auth.controller';
88
import { AuthService } from './auth.service';
99
import { JwtStrategy } from './strategies/jwt.strategy';
10+
import { GoogleStrategy } from './strategies/google.strategy';
1011

1112
import { GroupsModule } from '../groups/groups.module';
1213
import { RedisModule } from '../redis/redis.module';
@@ -27,7 +28,7 @@ import { RedisModule } from '../redis/redis.module';
2728
}),
2829
],
2930
controllers: [AuthController],
30-
providers: [AuthService, JwtStrategy],
31+
providers: [AuthService, JwtStrategy, GoogleStrategy],
3132
exports: [AuthService],
3233
})
3334
export class AuthModule {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { AuthGuard } from '@nestjs/passport';
3+
4+
@Injectable()
5+
export class GoogleOAuthGuard extends AuthGuard('google') {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { PassportStrategy } from '@nestjs/passport';
3+
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
4+
import { AppConfigService } from '../../config/app-config.service';
5+
6+
@Injectable()
7+
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
8+
constructor(config: AppConfigService) {
9+
super({
10+
clientID: config.googleClientId,
11+
clientSecret: config.googleClientSecret,
12+
callbackURL: `http://localhost:${config.port}/api/v1/auth/google/callback`,
13+
scope: ['email', 'profile'],
14+
});
15+
}
16+
17+
async validate(
18+
accessToken: string,
19+
refreshToken: string,
20+
profile: any,
21+
done: VerifyCallback,
22+
): Promise<any> {
23+
const { name, emails, photos } = profile;
24+
const user = {
25+
email: emails[0].value,
26+
name: name.givenName ? `${name.givenName} ${name.familyName || ''}`.trim() : profile.displayName,
27+
avatarUrl: photos && photos.length > 0 ? photos[0].value : undefined,
28+
};
29+
done(null, user);
30+
}
31+
}

0 commit comments

Comments
 (0)