import { Controller, Get, Post, Delete, Param, UseGuards, Request, Res, BadRequestException, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { Response } from 'express'; import { SsoService } from './sso.service'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; const COOKIE_NAME = 'ledgeriq_rt'; const isProduction = process.env.NODE_ENV === 'production'; @ApiTags('auth') @Controller('auth') export class SsoController { constructor( private ssoService: SsoService, private authService: AuthService, ) {} @Get('sso/providers') @ApiOperation({ summary: 'Get available SSO providers' }) getProviders() { return this.ssoService.getAvailableProviders(); } // Google OAuth routes would be: // GET /auth/google → passport.authenticate('google') // GET /auth/google/callback → passport callback // These are registered conditionally in auth.module.ts if env vars are set. // For now, we'll add the callback handler: @Get('google/callback') @ApiOperation({ summary: 'Google OAuth callback' }) async googleCallback(@Request() req: any, @Res() res: Response) { if (!req.user) { return res.redirect('/login?error=sso_failed'); } const result = await this.authService.generateTokenResponse(req.user); // Set refresh token cookie if (result.refreshToken) { res.cookie(COOKIE_NAME, result.refreshToken, { httpOnly: true, secure: isProduction, sameSite: 'strict', path: '/api/auth', maxAge: 30 * 24 * 60 * 60 * 1000, }); } // Redirect to app with access token in URL fragment (for SPA to pick up) return res.redirect(`/sso-callback?token=${result.accessToken}`); } @Get('azure/callback') @ApiOperation({ summary: 'Azure AD OAuth callback' }) async azureCallback(@Request() req: any, @Res() res: Response) { if (!req.user) { return res.redirect('/login?error=sso_failed'); } const result = await this.authService.generateTokenResponse(req.user); if (result.refreshToken) { res.cookie(COOKIE_NAME, result.refreshToken, { httpOnly: true, secure: isProduction, sameSite: 'strict', path: '/api/auth', maxAge: 30 * 24 * 60 * 60 * 1000, }); } return res.redirect(`/sso-callback?token=${result.accessToken}`); } @Post('sso/link') @ApiOperation({ summary: 'Link SSO provider to current user' }) @ApiBearerAuth() @UseGuards(JwtAuthGuard) async linkAccount(@Request() req: any) { // This would typically be done via the OAuth redirect flow // For now, it's a placeholder throw new BadRequestException('Use the OAuth redirect flow to link accounts'); } @Delete('sso/unlink/:provider') @ApiOperation({ summary: 'Unlink SSO provider from current user' }) @ApiBearerAuth() @UseGuards(JwtAuthGuard) async unlinkAccount(@Request() req: any, @Param('provider') provider: string) { await this.ssoService.unlinkSsoAccount(req.user.sub, provider); return { success: true }; } }