mirror of
https://github.com/hexastack/hexabot
synced 2025-04-10 15:55:55 +00:00
189 lines
5.5 KiB
TypeScript
189 lines
5.5 KiB
TypeScript
/*
|
|
* Copyright © 2025 Hexastack. All rights reserved.
|
|
*
|
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
|
*/
|
|
|
|
import {
|
|
BadRequestException,
|
|
Body,
|
|
Controller,
|
|
Get,
|
|
Inject,
|
|
InternalServerErrorException,
|
|
Param,
|
|
Post,
|
|
Req,
|
|
Res,
|
|
Session,
|
|
UnauthorizedException,
|
|
UseGuards,
|
|
UseInterceptors,
|
|
} from '@nestjs/common';
|
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
import { CsrfCheck, CsrfGen, CsrfGenAuth } from '@tekuconcept/nestjs-csrf';
|
|
import { Request, Response } from 'express';
|
|
import { Session as ExpressSession } from 'express-session';
|
|
|
|
import { config } from '@/config';
|
|
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
|
import { LoggerService } from '@/logger/logger.service';
|
|
import { Roles } from '@/utils/decorators/roles.decorator';
|
|
|
|
import { InvitationCreateDto } from '../dto/invitation.dto';
|
|
import { UserCreateDto } from '../dto/user.dto';
|
|
import { LocalAuthGuard } from '../guards/local-auth.guard';
|
|
import { InvitationService } from '../services/invitation.service';
|
|
import { UserService } from '../services/user.service';
|
|
import { ValidateAccountService } from '../services/validate-account.service';
|
|
|
|
export class BaseAuthController {
|
|
@Inject(EventEmitter2)
|
|
private readonly eventEmitter: EventEmitter2;
|
|
|
|
constructor(protected readonly logger: LoggerService) {}
|
|
|
|
/**
|
|
* Fetches details of the currently authenticated user.
|
|
*
|
|
* @param req - The request object which includes user details.
|
|
*
|
|
* @returns The user object from the request.
|
|
*/
|
|
@Get('me')
|
|
me(@Req() req: Request) {
|
|
return req.user;
|
|
}
|
|
|
|
/**
|
|
* Handles user logout by clearing the session and cookies.
|
|
*
|
|
* @param session - The current user's session to be destroyed.
|
|
* @param res - The response object used to clear the session cookie.
|
|
*
|
|
* @returns A status object indicating successful logout.
|
|
*/
|
|
@Post('logout')
|
|
@CsrfCheck(false)
|
|
@CsrfGen(false)
|
|
logout(
|
|
@Session() session: ExpressSession,
|
|
@Res({ passthrough: true }) res: Response,
|
|
) {
|
|
this.eventEmitter.emit('hook:user:logout', session);
|
|
res.clearCookie(config.session.name);
|
|
|
|
session.destroy((error) => {
|
|
if (error) {
|
|
this.logger.error(error);
|
|
throw new BadRequestException();
|
|
}
|
|
});
|
|
|
|
return { status: 'ok' };
|
|
}
|
|
}
|
|
|
|
@UseInterceptors(CsrfInterceptor)
|
|
@Controller('auth')
|
|
export class LocalAuthController extends BaseAuthController {
|
|
constructor(
|
|
logger: LoggerService,
|
|
private readonly userService: UserService,
|
|
private readonly validateAccountService: ValidateAccountService,
|
|
private readonly invitationService: InvitationService,
|
|
) {
|
|
super(logger);
|
|
}
|
|
|
|
/**
|
|
* Handles local login process using guard for public users.
|
|
*
|
|
* @param req - The request object containing user details.
|
|
*
|
|
* @returns The logged-in user object.
|
|
*/
|
|
@UseGuards(LocalAuthGuard)
|
|
@Roles('public')
|
|
@Post('local')
|
|
@CsrfCheck(false)
|
|
@CsrfGenAuth(true)
|
|
login(@Req() req: Request) {
|
|
return req.user;
|
|
}
|
|
|
|
/**
|
|
* Handles the signup process for new users.
|
|
*
|
|
* @param userCreateDto - Data Transfer Object containing the new user's information.
|
|
*
|
|
* @returns Success status object upon successful signup.
|
|
*/
|
|
@Roles('public')
|
|
@Post('signup')
|
|
async signup(@Body() userCreateDto: UserCreateDto) {
|
|
try {
|
|
await this.userService.create(userCreateDto);
|
|
return { success: true };
|
|
} catch (error) {
|
|
this.logger.error(error);
|
|
throw new BadRequestException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Accepts an invitation and creates a new user based on the invitation token.
|
|
* Verifies the token and ensures it matches the user details.
|
|
*
|
|
* @param userCreateDto - Data Transfer Object for the new user.
|
|
* @param token - The invitation token.
|
|
*
|
|
* @returns Void, upon successful creation of the user.
|
|
*/
|
|
@Roles('public')
|
|
@Post('accept-invite/:token')
|
|
async acceptInvite(
|
|
@Body() userCreateDto: UserCreateDto,
|
|
@Param('token') token: string,
|
|
) {
|
|
let decodedToken: InvitationCreateDto;
|
|
|
|
// Verify token
|
|
try {
|
|
decodedToken = await this.invitationService.verify(token);
|
|
} catch (error) {
|
|
if (error.name === 'TokenExpiredError')
|
|
throw new UnauthorizedException('Token expired');
|
|
else throw new BadRequestException(error.name, error.message);
|
|
}
|
|
|
|
// Verify token matches user
|
|
if (decodedToken.email !== userCreateDto.email)
|
|
throw new BadRequestException("Email doesn't match invitation email");
|
|
if (decodedToken.roles.some((item) => !userCreateDto.roles.includes(item)))
|
|
throw new BadRequestException('invitation roles do not match user roles');
|
|
|
|
try {
|
|
// Create user
|
|
await this.userService.create({ ...userCreateDto, state: false });
|
|
|
|
await this.validateAccountService.sendConfirmationEmail({
|
|
email: userCreateDto.email,
|
|
first_name: userCreateDto.first_name,
|
|
});
|
|
|
|
await this.invitationService.deleteOne({ email: decodedToken.email });
|
|
} catch (e) {
|
|
this.logger.error(
|
|
'Could not send email',
|
|
e.message,
|
|
e.stack,
|
|
'AcceptInvite',
|
|
);
|
|
throw new InternalServerErrorException('Could not send email');
|
|
}
|
|
}
|
|
}
|