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(
|
public async postLogin(
|
||||||
@Body() body: LoginUserDto,
|
@Body() body: LoginUserDto,
|
||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
@Req() request: Request,
|
@Req() req: Request,
|
||||||
): Promise<any> {
|
): 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) => {
|
const interaction = await this.authService.checkInteractionStatus(req, res);
|
||||||
res.cookie(cookie.name, cookie.value, cookie.options);
|
|
||||||
});
|
|
||||||
|
|
||||||
// if loginRedirect cookie is set, redirect to that page.
|
if (!interaction) {
|
||||||
|
return await this.authService.createSession(res, userId);
|
||||||
console.log(request.cookies);
|
|
||||||
if (request.cookies['interactionId']) {
|
|
||||||
console.log('interactionRedirect');
|
|
||||||
return {
|
|
||||||
status: 'interactionRedirect',
|
|
||||||
interactionId: request.cookies['interactionId'],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Logged in successfully');
|
return await this.authService.loginExistingInteraction(req, res, userId);
|
||||||
return {
|
|
||||||
status: 'success',
|
|
||||||
message: 'Logged in successfully',
|
|
||||||
sessionId: sessionData.sessionId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Body,
|
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
|
@ -19,7 +18,6 @@ import { InteractionService } from '../oidc/service/interaction.service';
|
||||||
import { OidcService } from '../oidc/service/core.service';
|
import { OidcService } from '../oidc/service/core.service';
|
||||||
import { InteractionParams, InteractionSession } from '../oidc/types/interaction.type';
|
import { InteractionParams, InteractionSession } from '../oidc/types/interaction.type';
|
||||||
import { User as UserObject } from 'src/database/models/user.model';
|
import { User as UserObject } from 'src/database/models/user.model';
|
||||||
import { LoginUserDto } from '../dto/loginUser.dto';
|
|
||||||
|
|
||||||
@Controller('interaction')
|
@Controller('interaction')
|
||||||
@ApiExcludeController()
|
@ApiExcludeController()
|
||||||
|
@ -58,22 +56,15 @@ export class InteractionController {
|
||||||
|
|
||||||
const cookies = req.cookies;
|
const cookies = req.cookies;
|
||||||
|
|
||||||
res.cookie('interactionId', uid, { httpOnly: true, path: '/auth/login', secure: true });
|
// Log in console when the packet expires
|
||||||
res.cookie('_interaction', cookies['_interaction'], {
|
|
||||||
httpOnly: true,
|
|
||||||
path: '/auth/login',
|
|
||||||
secure: true,
|
|
||||||
expires: new Date(Date.now() + 900000),
|
|
||||||
});
|
|
||||||
|
|
||||||
res.cookie('_interaction.sig', cookies['_interaction.sig'], {
|
console.log(cookies);
|
||||||
httpOnly: true,
|
|
||||||
path: '/auth/login',
|
|
||||||
secure: true,
|
|
||||||
expires: new Date(Date.now() + 900000),
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.redirect('/auth/login');
|
return await this.interactionService.interactionRedirectToLoginPage(
|
||||||
|
res,
|
||||||
|
uid,
|
||||||
|
cookies['_interaction.sig'],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'consent': {
|
case 'consent': {
|
||||||
|
@ -94,6 +85,7 @@ export class InteractionController {
|
||||||
return res.render('interaction/consent', {
|
return res.render('interaction/consent', {
|
||||||
client: {
|
client: {
|
||||||
clientName: details.params.client_id,
|
clientName: details.params.client_id,
|
||||||
|
//TODO: Actually have this work
|
||||||
clientLogo: 'https://via.placeholder.com/150',
|
clientLogo: 'https://via.placeholder.com/150',
|
||||||
},
|
},
|
||||||
uid,
|
uid,
|
||||||
|
@ -117,20 +109,4 @@ export class InteractionController {
|
||||||
async denyInteraction(@Req() req: Request, @Res() res: Response) {
|
async denyInteraction(@Req() req: Request, @Res() res: Response) {
|
||||||
return this.interactionService.abort(req, res);
|
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 INTERACTION_LIFE = 1 * 60 * 60; // 1 hour
|
||||||
export const PUSHED_AUTH_REQ_LIFE = 15 * 60; // 15 minutes
|
export const PUSHED_AUTH_REQ_LIFE = 15 * 60; // 15 minutes
|
||||||
export const CLIENT_CREDENTIALS_TOKEN_LIFE = 7 * 24 * 60 * 60; // 7 days
|
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 { Interaction } from '../types/interaction.type';
|
||||||
import { OidcService } from './core.service';
|
import { OidcService } from './core.service';
|
||||||
|
import { createInteractionExpiry } from '../oidc.const';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InteractionService {
|
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
|
* Get a user from the interaction
|
||||||
* @param req The request
|
* @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 { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { Response, Request } from 'express';
|
||||||
|
|
||||||
import { UserService } from '../../user/service/user.service';
|
import { UserService } from '../../user/service/user.service';
|
||||||
import { RedisService } from '../../redis/service/redis.service';
|
import { RedisService } from '../../redis/service/redis.service';
|
||||||
|
@ -11,6 +12,8 @@ import {
|
||||||
PASSWORD_RESET_EXPIRATION,
|
PASSWORD_RESET_EXPIRATION,
|
||||||
} from '../auth.const';
|
} from '../auth.const';
|
||||||
import { OidcService } from '../oidc/service/core.service';
|
import { OidcService } from '../oidc/service/core.service';
|
||||||
|
import { InteractionService } from '../oidc/service/interaction.service';
|
||||||
|
import { Interaction } from '../oidc/types/interaction.type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
@ -18,6 +21,7 @@ export class AuthService {
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly redisService: RedisService,
|
private readonly redisService: RedisService,
|
||||||
private readonly oidcService: OidcService,
|
private readonly oidcService: OidcService,
|
||||||
|
private readonly interactionService: InteractionService,
|
||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -51,41 +55,75 @@ export class AuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a user's login credentials and return the session cookies
|
* Fetches an Interaction from the Interaction Service. Will return null if one is not found.
|
||||||
* @param username The username or email address of the user
|
* @param req The Request Object
|
||||||
* @param password The password of the user
|
* @param res The Response Object
|
||||||
* @returns The session cookies
|
* @returns Interaction | null
|
||||||
*/
|
*/
|
||||||
public async login(username: string, password: string): Promise<any> {
|
public async checkInteractionStatus(req: Request, res: Response): Promise<Interaction | null> {
|
||||||
const user = await this.userService.authenticate(username, password);
|
try {
|
||||||
|
return await this.interactionService.get(req, res);
|
||||||
if (!user) {
|
} catch (e) {
|
||||||
throw new BadRequestException('Invalid credentials');
|
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 username The username or email address of the user
|
||||||
* @param password The password of the user
|
* @param password The password of the user
|
||||||
* @returns The user id
|
* @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);
|
const user = await this.userService.authenticate(username, password);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new BadRequestException('Invalid credentials');
|
throw new UnauthorizedException('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.id;
|
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 == //
|
// == 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 {
|
} else {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
if (responseData.status === 'interactionRedirect' && responseData.redirectUrl) {
|
if (responseData.status === 'redirect' && responseData.redirectUrl) {
|
||||||
window.location.href = responseData.redirectUrl;
|
window.location.href = responseData.redirectUrl;
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '/auth/auth-test';
|
window.location.href = '/auth/auth-test';
|
||||||
|
|
Loading…
Reference in a new issue