import { Body, Controller, Get, Inject, Logger, Param, Post, Query, Res, UsePipes, ValidationPipe, } from '@nestjs/common'; import { Response } from 'express'; import { IncomingAuthDto, IncomingAuthLoginDto } from './dto/incoming-auth.dto'; import { AccountService } from './account.service'; import { LoginDto } from './dto/login.dto'; import { UserService } from '../user/user.service'; import { AuthService } from '../auth/auth.service'; import { CreateUserDtoWithCaptcha } from '../user/dto/create-user.dto'; import { ApplicationService } from '../application/application.service'; import { AccessUserDetailsDto } from './dto/access-user-details.dto'; import { MailerService } from '../mailer/mailer.service'; import { RequestPasswordResetDto } from '../user/dto/request-password-reset.dto'; import { ResetPasswordDto } from '../user/dto/reset-password.dto'; import { appData } from '../../config/appData'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; @Controller('account') export class AccountController { // private readonly logger = new Logger('account'); constructor( private readonly accountService: AccountService, @Inject(UserService) private readonly userService: UserService, @Inject(AuthService) private readonly authService: AuthService, @Inject(ApplicationService) private readonly applicationService: ApplicationService, @Inject(MailerService) private readonly mailerService: MailerService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger = new Logger('account'), ) {} /** * OAUTH FLOW HANDLERS :: Displayed only when invoded mid-way auth flow */ /** * to display login form on client-initiated-auth */ @Get('o/login') @UsePipes( new ValidationPipe({ disableErrorMessages: false, }), ) async showLoginPageAsAuth(@Res() res: Response, @Query() incomingAuthDto: IncomingAuthDto) { const { client_id } = incomingAuthDto; try { const applicationDetails = await this.accountService.validateAccessRequest(incomingAuthDto); return res.render('account/o/login', { app: applicationDetails, project_name: appData.Name }); } catch (e) { this.logger.error(`${e.message} for ${client_id}`); return res.render('error', e.response); } } /** * To handle login form submission on client-initiated-auth */ @Post('o/login') @UsePipes( new ValidationPipe({ disableErrorMessages: false, }), ) async processLoginPageAsAuth(@Res() res: Response, @Body() incomingAuthDto: IncomingAuthLoginDto) { const { client_id } = incomingAuthDto; /** * validate and get application details from incoming dto */ try { const applicationDetails = await this.accountService.validateAccessRequest(incomingAuthDto); /** * ensure authentication for users */ try { const { token, user } = await this.accountService.authenticateAndGenerateToken(incomingAuthDto); /** push user into application participant list set */ this.applicationService.pushUserIntoApplicationParticipantList(applicationDetails, user); this.userService.pushApplicationIntoUserParticipantList(applicationDetails, user); res.redirect(`${incomingAuthDto.redirect_uri}/?access_token=${token}`); } catch (e) { /** * Render login page with error message from server */ this.logger.error(`${e.message} for ${client_id}`); return res.render('account/o/login', { app: applicationDetails, project_name: appData.Name, server: { message: e.message }, }); } } catch (e) { /** * Render error page with validation error mesage */ this.logger.error(`POST ${e.message} for ${client_id}`); return res.render('error', e.response); } } @Post('o/access') @UsePipes(ValidationPipe) async shareUserDetailsViaAuth(@Body() accessUserDetailsDto: AccessUserDetailsDto) { return this.accountService.provideUserDetailOnAccess(accessUserDetailsDto); } /** * NON OAUTH FLOW HANDLERS :: Normal Operations */ /** * to display login page */ @Get('login') @UsePipes( new ValidationPipe({ disableErrorMessages: false, }), ) async showLoginPage(@Res() res: Response) { try { return res.render('account/login', { project_name: appData.Name }); } catch (e) { return res.render('error', e.response); } } /** * to logout user from module */ @Get('logout') async logoutUser(@Res() res: Response) { try { res.cookie('vitAuth', ''); res.redirect('./../'); } catch (e) { return res.render('error', e.response); } } @Post('login') @UsePipes(ValidationPipe) async processLoginPage(@Res() res: Response, @Body() loginDto: LoginDto) { try { const user = await this.userService.login(loginDto); const jwtData = { id: user._id, email: user.collegeEmail }; const cookieData = await this.authService.generateJwt(jwtData); res.cookie('vitAuth', cookieData); // res.render('profile/homepage', user); res.redirect('./../dashboard'); } catch (e) { return res.render('account/login', { server: { message: e.message }, project_name: appData.Name }); } } /** * To show reset password page */ @Get('/password/request') async showPasswordRequestPage(@Res() res: Response) { try { return res.render('password/request', { project_name: appData.Name }); } catch (e) { return res.render('error', e.response); } } @Post('/password/request') @UsePipes(ValidationPipe) async processRequestPage(@Res() res: Response, @Body() requestPasswordResetDto: RequestPasswordResetDto) { try { const response = await this.userService.request(requestPasswordResetDto); const templateData = { server: { message: 'please check your email for password reset link', }, }; this.mailerService.sendPasswordResetLink(response.collegeEmail); return res.render('account/login', { templateData, project_name: appData.Name }); } catch (e) { const templateData = { server: e.response, }; return res.render('account/login', templateData); } } @Get('/password/reset/:token') async showPasswordResetPage(@Res() res: Response, @Param('token') token: string) { try { this.mailerService.checkPasswordResetToken(token); return res.render('password/reset', { project_name: appData.Name }); } catch (e) { return res.render('error', e.response); } } @Post('/password/reset/:token') async processResetPage( @Res() res: Response, @Param('token') token: string, @Body() resetPasswordDto: ResetPasswordDto, ) { try { const isValidToken = await this.mailerService.checkPasswordResetToken(token); await this.userService.reset(resetPasswordDto, isValidToken); const templateData = { server: { message: 'password changed successfully', }, }; return res.render('account/login', { templateData, project_name: appData.Name }); } catch (e) { const templateData = { server: e.response, }; return res.render('account/register', templateData); } } /** * Pages to register a new user into the system */ @Get('/register') async showRegisterPage(@Res() res: Response) { try { return res.render('account/register', { project_name: appData.Name }); } catch (e) { return res.render('error', e.response); } } /** * Page to receive verification callback from email */ @Get('/register/verify/:token') async showRegisterSuccessPage(@Res() res: Response, @Param('token') token: string) { try { this.mailerService.checkVerificationToken(token); return res.render('account/register/verify'); } catch (e) { return res.render('error', e.response); } } @Post('/register') @UsePipes(ValidationPipe) async processRegisterPage(@Res() res: Response, @Body() createUserDtoWithCaptcha: CreateUserDtoWithCaptcha) { try { const response = await this.userService.create(createUserDtoWithCaptcha); const templateData = { server: { message: 'please check your email for verification link', }, }; // this.mailerService.sendEmail(response.collegeEmail); return res.render('account/register', { templateData, project_name: appData.Name }); } catch (e) { const templateData = { server: e.response, }; return res.render('account/register', { templateData, project_name: appData.Name }); } } }