From ea98ed79760b78f7f427c55d77b73fbe5d0e93f7 Mon Sep 17 00:00:00 2001 From: Kakious Date: Tue, 15 Oct 2024 12:14:16 -0400 Subject: [PATCH] feat: working interaction login --- src/auth/controllers/auth.controller.ts | 26 ++----- .../controllers/interaction.controller.ts | 40 ++-------- src/auth/oidc/oidc.const.ts | 8 ++ src/auth/oidc/service/interaction.service.ts | 37 ++++++++++ src/auth/services/auth.service.ts | 74 ++++++++++++++----- src/s3/s3.service.ts | 4 + views/auth/login.hbs | 2 +- 7 files changed, 120 insertions(+), 71 deletions(-) diff --git a/src/auth/controllers/auth.controller.ts b/src/auth/controllers/auth.controller.ts index eebf57a..ad8642c 100644 --- a/src/auth/controllers/auth.controller.ts +++ b/src/auth/controllers/auth.controller.ts @@ -19,31 +19,17 @@ export class AuthController { public async postLogin( @Body() body: LoginUserDto, @Res({ passthrough: true }) res: Response, - @Req() request: Request, + @Req() req: Request, ): Promise { - const sessionData = await this.authService.login(body.username, body.password); + const userId = await this.authService.login(body.username, body.password); - sessionData.cookiesForms.forEach((cookie) => { - res.cookie(cookie.name, cookie.value, cookie.options); - }); + const interaction = await this.authService.checkInteractionStatus(req, res); - // if loginRedirect cookie is set, redirect to that page. - - console.log(request.cookies); - if (request.cookies['interactionId']) { - console.log('interactionRedirect'); - return { - status: 'interactionRedirect', - interactionId: request.cookies['interactionId'], - }; + if (!interaction) { + return await this.authService.createSession(res, userId); } - console.log('Logged in successfully'); - return { - status: 'success', - message: 'Logged in successfully', - sessionId: sessionData.sessionId, - }; + return await this.authService.loginExistingInteraction(req, res, userId); } @Post('register') diff --git a/src/auth/controllers/interaction.controller.ts b/src/auth/controllers/interaction.controller.ts index 762b0cf..df14fbc 100644 --- a/src/auth/controllers/interaction.controller.ts +++ b/src/auth/controllers/interaction.controller.ts @@ -1,6 +1,5 @@ import { BadRequestException, - Body, Controller, Get, InternalServerErrorException, @@ -19,7 +18,6 @@ import { InteractionService } from '../oidc/service/interaction.service'; import { OidcService } from '../oidc/service/core.service'; import { InteractionParams, InteractionSession } from '../oidc/types/interaction.type'; import { User as UserObject } from 'src/database/models/user.model'; -import { LoginUserDto } from '../dto/loginUser.dto'; @Controller('interaction') @ApiExcludeController() @@ -58,22 +56,15 @@ export class InteractionController { const cookies = req.cookies; - res.cookie('interactionId', uid, { httpOnly: true, path: '/auth/login', secure: true }); - res.cookie('_interaction', cookies['_interaction'], { - httpOnly: true, - path: '/auth/login', - secure: true, - expires: new Date(Date.now() + 900000), - }); + // Log in console when the packet expires - res.cookie('_interaction.sig', cookies['_interaction.sig'], { - httpOnly: true, - path: '/auth/login', - secure: true, - expires: new Date(Date.now() + 900000), - }); + console.log(cookies); - return res.redirect('/auth/login'); + return await this.interactionService.interactionRedirectToLoginPage( + res, + uid, + cookies['_interaction.sig'], + ); } case 'consent': { @@ -94,6 +85,7 @@ export class InteractionController { return res.render('interaction/consent', { client: { clientName: details.params.client_id, + //TODO: Actually have this work clientLogo: 'https://via.placeholder.com/150', }, uid, @@ -117,20 +109,4 @@ export class InteractionController { async denyInteraction(@Req() req: Request, @Res() res: Response) { return this.interactionService.abort(req, res); } - - @Post(':id/login') - async loginInteraction(@Body() login: LoginUserDto, @Req() req: Request, @Res() res: Response) { - const userId = await this.authService.validateLogin(login.username, login.password); - - if (!userId) { - throw new BadRequestException('Invalid login'); - } - - const redirectUrl = await this.interactionService.login(req, res, userId, true); - - res.json({ - redirectUrl, - status: 'interactionRedirect', - }); - } } diff --git a/src/auth/oidc/oidc.const.ts b/src/auth/oidc/oidc.const.ts index be2a0d9..c008ea7 100644 --- a/src/auth/oidc/oidc.const.ts +++ b/src/auth/oidc/oidc.const.ts @@ -13,3 +13,11 @@ export const GRANT_LIFE = 60 * 60 * 24 * 30; // 1 month export const INTERACTION_LIFE = 1 * 60 * 60; // 1 hour export const PUSHED_AUTH_REQ_LIFE = 15 * 60; // 15 minutes export const CLIENT_CREDENTIALS_TOKEN_LIFE = 7 * 24 * 60 * 60; // 7 days + +//====Helper functions==== + +/** + * Creates a new date for the cookie expiry based on the interaction life + * @returns Date + */ +export const createInteractionExpiry = () => new Date(Date.now() + INTERACTION_LIFE * 1000); diff --git a/src/auth/oidc/service/interaction.service.ts b/src/auth/oidc/service/interaction.service.ts index 7c98ede..b78fe52 100644 --- a/src/auth/oidc/service/interaction.service.ts +++ b/src/auth/oidc/service/interaction.service.ts @@ -10,6 +10,7 @@ import { Span } from 'nestjs-otel'; import { Interaction } from '../types/interaction.type'; import { OidcService } from './core.service'; +import { createInteractionExpiry } from '../oidc.const'; @Injectable() export class InteractionService { @@ -33,6 +34,9 @@ export class InteractionService { } } + // TODO: Does a direct check inside of redis and returns a true/false depending if the session exists. + public async checkExisting() {} + /** * Get a user from the interaction * @param req The request @@ -215,4 +219,37 @@ export class InteractionService { }); } } + + /** + * + */ + + async interactionRedirectToLoginPage( + res: Response, + interactionId: string, + interactionSig: string, + ) { + res.cookie('interactionId', interactionId, { + httpOnly: true, + path: '/auth/login', + secure: true, + expires: createInteractionExpiry(), + }); + + res.cookie('_interaction', interactionId, { + httpOnly: true, + path: '/auth/login', + secure: true, + expires: createInteractionExpiry(), + }); + + res.cookie('_interaction.sig', interactionSig, { + httpOnly: true, + path: '/auth/login', + secure: true, + expires: createInteractionExpiry(), + }); + + res.redirect('/auth/login'); + } } diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 8da6cb5..5476d46 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common'; +import { Response, Request } from 'express'; import { UserService } from '../../user/service/user.service'; import { RedisService } from '../../redis/service/redis.service'; @@ -11,6 +12,8 @@ import { PASSWORD_RESET_EXPIRATION, } from '../auth.const'; import { OidcService } from '../oidc/service/core.service'; +import { InteractionService } from '../oidc/service/interaction.service'; +import { Interaction } from '../oidc/types/interaction.type'; @Injectable() export class AuthService { @@ -18,6 +21,7 @@ export class AuthService { private readonly userService: UserService, private readonly redisService: RedisService, private readonly oidcService: OidcService, + private readonly interactionService: InteractionService, private readonly mailService: MailService, ) {} @@ -51,41 +55,75 @@ export class AuthService { } /** - * Validate a user's login credentials and return the session cookies - * @param username The username or email address of the user - * @param password The password of the user - * @returns The session cookies + * Fetches an Interaction from the Interaction Service. Will return null if one is not found. + * @param req The Request Object + * @param res The Response Object + * @returns Interaction | null */ - public async login(username: string, password: string): Promise { - const user = await this.userService.authenticate(username, password); - - if (!user) { - throw new BadRequestException('Invalid credentials'); + public async checkInteractionStatus(req: Request, res: Response): Promise { + try { + return await this.interactionService.get(req, res); + } catch (e) { + return null; } - - if (!user.emailVerified) { - throw new UnauthorizedException('Email not verified'); - } - - return this.oidcService.createSession(user.id); } /** - * Validate a user's login credential and return the user id + * Validate a user's login credentials * @param username The username or email address of the user * @param password The password of the user * @returns The user id */ - public async validateLogin(username: string, password: string): Promise { + public async login(username: string, password: string): Promise { const user = await this.userService.authenticate(username, password); if (!user) { - throw new BadRequestException('Invalid credentials'); + throw new UnauthorizedException('Invalid credentials'); } return user.id; } + //TODO: Make response DTOs + /** + * Create a session for a user and set the session copokies. + * @param response The Response object + * @param userId The User's ID + * @returns + */ + public async createSession(res: Response, userId: string) { + const sessionData = await this.oidcService.createSession(userId); + + sessionData.cookiesForms.forEach((cookie) => { + res.cookie(cookie.name, cookie.value, { + ...cookie.options, + sameSite: cookie.options.sameSite as 'strict' | 'lax' | 'none' | undefined, + }); + }); + + return { + status: 'success', + message: 'Login Successful', + sessionId: sessionData.sessionId, + }; + } + + /** + * This will login an existing interaction and instruct the browser to redirect to the interaction flow + * @param interaction The interaction object + * @param userId The User's ID + * @return + */ + public async loginExistingInteraction(req: Request, res: Response, userId: string) { + const redirectUrl = await this.interactionService.login(req, res, userId, true); + + return { + status: 'redirect', + message: 'Login Successful, Redirect to session page', + redirectUrl, + }; + } + // == Password Reset Logic == // /** diff --git a/src/s3/s3.service.ts b/src/s3/s3.service.ts index e69de29..d6b8796 100644 --- a/src/s3/s3.service.ts +++ b/src/s3/s3.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class S3Service {} diff --git a/views/auth/login.hbs b/views/auth/login.hbs index 33b4f68..0da715b 100644 --- a/views/auth/login.hbs +++ b/views/auth/login.hbs @@ -196,7 +196,7 @@ document.getElementById('loginForm').addEventListener('submit', async function(e } } else { const responseData = await response.json(); - if (responseData.status === 'interactionRedirect' && responseData.redirectUrl) { + if (responseData.status === 'redirect' && responseData.redirectUrl) { window.location.href = responseData.redirectUrl; } else { window.location.href = '/auth/auth-test';