Files
goodgo-platform/apps/api/src/modules/auth/presentation/controllers/oauth.controller.ts
Ho Ngoc Hai c920934fb6 fix(lint): enforce consistent-type-imports and fix import ordering across codebase
Auto-fix 862 lint errors: convert value imports used only as types to
`import type`, fix import group ordering in seed.ts and du-an-api.ts,
remove unused imports in auth controller, and clean up stale eslint-disable
comments referencing non-existent rules.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 05:13:56 +07:00

113 lines
3.8 KiB
TypeScript

import {
Controller,
Get,
Query,
Req,
Res,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import { type Request, type Response } from 'express';
import { UnauthorizedException } from '@modules/shared';
import { type TokenPair } from '../../infrastructure/services/token.service';
import { type ZaloOAuthStrategy } from '../../infrastructure/strategies/zalo-oauth.strategy';
import { GoogleOAuthGuard } from '../guards/google-oauth.guard';
const IS_PRODUCTION = process.env['NODE_ENV'] === 'production';
const ACCESS_TOKEN_MAX_AGE = 15 * 60 * 1000;
const REFRESH_TOKEN_MAX_AGE = 30 * 24 * 60 * 60 * 1000;
const AUTH_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 * 1000;
const FRONTEND_URL = process.env['FRONTEND_URL'] ?? 'http://localhost:3000';
function setAuthCookies(res: Response, tokens: TokenPair): void {
res.cookie('access_token', tokens.accessToken, {
httpOnly: true,
secure: IS_PRODUCTION,
sameSite: 'strict',
path: '/',
maxAge: ACCESS_TOKEN_MAX_AGE,
});
res.cookie('refresh_token', tokens.refreshToken, {
httpOnly: true,
secure: IS_PRODUCTION,
sameSite: 'strict',
path: '/auth',
maxAge: REFRESH_TOKEN_MAX_AGE,
});
res.cookie('goodgo_authenticated', '1', {
httpOnly: false,
secure: IS_PRODUCTION,
sameSite: 'lax',
path: '/',
maxAge: AUTH_COOKIE_MAX_AGE,
});
}
@ApiTags('auth')
@Controller('auth')
export class OAuthController {
constructor(private readonly zaloStrategy: ZaloOAuthStrategy) {}
// ─── Google OAuth ──────────────────────────────────────────────────
@Get('google')
@UseGuards(GoogleOAuthGuard)
@ApiOperation({ summary: 'Initiate Google OAuth2 login' })
@ApiResponse({ status: 302, description: 'Redirect to Google consent screen' })
googleLogin(): void {
// Guard handles redirect to Google
}
@Throttle({ default: { ttl: 3_600_000, limit: 10 } })
@Get('google/callback')
@UseGuards(GoogleOAuthGuard)
@ApiOperation({ summary: 'Google OAuth2 callback' })
@ApiResponse({ status: 302, description: 'Redirect to frontend with auth cookies' })
async googleCallback(
@Req() req: Request,
@Res() res: Response,
): Promise<void> {
const tokens = req.user as TokenPair | undefined;
if (!tokens?.accessToken) {
throw new UnauthorizedException('Google authentication failed');
}
setAuthCookies(res, tokens);
res.redirect(`${FRONTEND_URL}/auth/callback?provider=google`);
}
// ─── Zalo OAuth ────────────────────────────────────────────────────
@Get('zalo')
@ApiOperation({ summary: 'Initiate Zalo OAuth2 login' })
@ApiResponse({ status: 302, description: 'Redirect to Zalo consent screen' })
zaloLogin(@Res() res: Response): void {
const authUrl = this.zaloStrategy.getAuthorizationUrl();
res.redirect(authUrl);
}
@Throttle({ default: { ttl: 3_600_000, limit: 10 } })
@Get('zalo/callback')
@ApiOperation({ summary: 'Zalo OAuth2 callback' })
@ApiResponse({ status: 302, description: 'Redirect to frontend with auth cookies' })
async zaloCallback(
@Query('code') code: string,
@Query('code_verifier') codeVerifier: string | undefined,
@Res() res: Response,
): Promise<void> {
if (!code) {
throw new UnauthorizedException('Zalo authorization code missing');
}
try {
const result = await this.zaloStrategy.handleCallback(code, codeVerifier);
setAuthCookies(res, result.tokens);
res.redirect(`${FRONTEND_URL}/auth/callback?provider=zalo`);
} catch {
res.redirect(`${FRONTEND_URL}/auth/callback?error=zalo_auth_failed`);
}
}
}