feat: working interaction login

This commit is contained in:
Kakious 2024-10-15 12:14:16 -04:00
parent e3ba6bf1fd
commit ea98ed7976
7 changed files with 120 additions and 71 deletions

View file

@ -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<any> {
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')

View file

@ -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',
});
}
}

View file

@ -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);

View file

@ -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');
}
}

View file

@ -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<any> {
const user = await this.userService.authenticate(username, password);
if (!user) {
throw new BadRequestException('Invalid credentials');
public async checkInteractionStatus(req: Request, res: Response): Promise<Interaction | null> {
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<string> {
public async login(username: string, password: string): Promise<string> {
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 == //
/**

View file

@ -0,0 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class S3Service {}

View file

@ -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';