feat: working interaction login
This commit is contained in:
parent
e3ba6bf1fd
commit
ea98ed7976
7 changed files with 120 additions and 71 deletions
|
@ -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')
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 == //
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class S3Service {}
|
|
@ -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';
|
||||
|
|
Loading…
Reference in a new issue