feat: implement swagger plugin

refactor: move views into a seperate controller for ease of management
This commit is contained in:
Kakious 2024-10-15 12:58:41 -04:00
parent ea98ed7976
commit 178d922f24
14 changed files with 145 additions and 72 deletions

View file

@ -3,6 +3,7 @@
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true "deleteOutDir": true,
"plugins": ["@nestjs/swagger"]
} }
} }

View file

@ -19,6 +19,8 @@ import { BullModule } from '@nestjs/bullmq';
import { BullConfigService } from './redis/service/bull-config.service'; import { BullConfigService } from './redis/service/bull-config.service';
import { RedisService } from './redis/service/redis.service'; import { RedisService } from './redis/service/redis.service';
import { OrganizationModule } from './organization/organization.module'; import { OrganizationModule } from './organization/organization.module';
import { ClientModule } from './client/client.module';
import { ViewModule } from './view/view.module';
@Module({ @Module({
imports: [ imports: [
@ -68,6 +70,8 @@ import { OrganizationModule } from './organization/organization.module';
UserModule, UserModule,
AuthModule, AuthModule,
OrganizationModule, OrganizationModule,
ClientModule,
ViewModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],

View file

@ -1,5 +1,5 @@
import { Body, Controller, Get, Post, Query, Render, Req, Res, UseGuards } from '@nestjs/common'; import { Body, Controller, Get, Post, Query, Render, Req, Res, UseGuards } from '@nestjs/common';
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger'; import { ApiExcludeEndpoint, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { ForgotPasswordDto } from '../dto/forgotPassword.dto'; import { ForgotPasswordDto } from '../dto/forgotPassword.dto';
@ -8,6 +8,7 @@ import { LoginUserDto } from '../dto/loginUser.dto';
import { User } from '../decorators/user.decorator'; import { User } from '../decorators/user.decorator';
import { LoginGuard } from '../guard/login.guard'; import { LoginGuard } from '../guard/login.guard';
import { Response, Request } from 'express'; import { Response, Request } from 'express';
import { LoginResponse } from '../dto/loginResponse.dto';
// TODO: Implement RateLimit // TODO: Implement RateLimit
@Controller('auth') @Controller('auth')
@ -20,7 +21,7 @@ export class AuthController {
@Body() body: LoginUserDto, @Body() body: LoginUserDto,
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Req() req: Request, @Req() req: Request,
): Promise<any> { ): Promise<LoginResponse> {
const userId = await this.authService.login(body.username, body.password); const userId = await this.authService.login(body.username, body.password);
const interaction = await this.authService.checkInteractionStatus(req, res); const interaction = await this.authService.checkInteractionStatus(req, res);
@ -49,50 +50,6 @@ export class AuthController {
// ==== Render pages ==== // // ==== Render pages ==== //
@Get('login')
@UseGuards(LoginGuard)
@Render('auth/login')
@ApiExcludeEndpoint()
public async getHello(): Promise<any> {
return {
forgot_password: 'forgot-password',
register: 'register',
login_url: '/auth/login',
//background_image: 'https://waterwolf.club/static/img/portal/portal7.jpg',
};
}
@Get('login/totp')
@UseGuards(LoginGuard)
@Render('auth/login-totp')
@ApiExcludeEndpoint()
public async getLoginTotp(): Promise<any> {
return {
login: 'login',
methods: ['authenticator', 'email'],
};
}
@Get('register')
@UseGuards(LoginGuard)
@Render('auth/register')
@ApiExcludeEndpoint()
public async getRegister(): Promise<any> {
return {
login: 'login',
};
}
@Get('forgot-password')
@UseGuards(LoginGuard)
@Render('auth/forgot-password')
@ApiExcludeEndpoint()
public async getForgotPassword(): Promise<any> {
return {
login: 'login',
};
}
@Get('verify-email') @Get('verify-email')
@UseGuards(LoginGuard) @UseGuards(LoginGuard)
@ApiExcludeEndpoint() @ApiExcludeEndpoint()
@ -121,10 +78,4 @@ export class AuthController {
response.redirect('/auth/login'); response.redirect('/auth/login');
} }
@Get('auth-test')
@ApiExcludeEndpoint()
public async getAuthTest(@User() user: any): Promise<any> {
return user;
}
} }

View file

@ -38,7 +38,6 @@ export class InteractionController {
prompt: any; prompt: any;
params: InteractionParams; params: InteractionParams;
} = await this.interactionService.get(req, res).catch((err: unknown) => { } = await this.interactionService.get(req, res).catch((err: unknown) => {
console.log(err);
//TODO: Handle error in a nice way. //TODO: Handle error in a nice way.
throw new BadRequestException("Couldn't get interaction details", { cause: err }); throw new BadRequestException("Couldn't get interaction details", { cause: err });
}); });
@ -46,24 +45,14 @@ export class InteractionController {
const { uid, prompt, params } = details; const { uid, prompt, params } = details;
switch (prompt.name) { switch (prompt.name) {
case 'login': { case 'login': {
//Set a login redirect cookie to redirect the user back to the interaction page after login.
// Take the interaction cookies and write them to /auth/login
// _interaction, _interaction.sig, interactionId
if (!uid) { if (!uid) {
throw new InternalServerErrorException('No uid found'); throw new InternalServerErrorException('No uid found');
} }
const cookies = req.cookies;
// Log in console when the packet expires
console.log(cookies);
return await this.interactionService.interactionRedirectToLoginPage( return await this.interactionService.interactionRedirectToLoginPage(
res, res,
uid, uid,
cookies['_interaction.sig'], req.cookies['_interaction.sig'],
); );
} }

View file

@ -0,0 +1,14 @@
export class LoginResponse {
status: LoginStatusResponse;
message: string;
sessionId?: string;
redirectUrl?: string;
}
export enum LoginStatusResponse {
SUCCESS = 'success',
REDIRECT = 'redirect',
}

View file

@ -10,6 +10,7 @@ export class LoginGuard implements CanActivate {
const authType = this.clsService.get('authType'); const authType = this.clsService.get('authType');
const response = context.switchToHttp().getResponse() as Response; const response = context.switchToHttp().getResponse() as Response;
console.log('test');
if (authType === 'session') { if (authType === 'session') {
response.redirect('/auth/auth-test'); response.redirect('/auth/auth-test');
return false; return false;

View file

@ -0,0 +1,10 @@
export const scopesArray = {
profile: {
scope: 'profile',
displayName: 'General Profile Information',
},
email: {
scope: 'email',
displayName: '',
},
};

View file

@ -1,4 +1,9 @@
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common'; import {
BadRequestException,
Injectable,
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';
import { Response, Request } from 'express'; import { Response, Request } from 'express';
import { UserService } from '../../user/service/user.service'; import { UserService } from '../../user/service/user.service';
@ -14,6 +19,7 @@ import {
import { OidcService } from '../oidc/service/core.service'; import { OidcService } from '../oidc/service/core.service';
import { InteractionService } from '../oidc/service/interaction.service'; import { InteractionService } from '../oidc/service/interaction.service';
import { Interaction } from '../oidc/types/interaction.type'; import { Interaction } from '../oidc/types/interaction.type';
import { LoginResponse, LoginStatusResponse } from '../dto/loginResponse.dto';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -84,14 +90,13 @@ export class AuthService {
return user.id; return user.id;
} }
//TODO: Make response DTOs
/** /**
* Create a session for a user and set the session copokies. * Create a session for a user and set the session copokies.
* @param response The Response object * @param response The Response object
* @param userId The User's ID * @param userId The User's ID
* @returns * @returns
*/ */
public async createSession(res: Response, userId: string) { public async createSession(res: Response, userId: string): Promise<LoginResponse> {
const sessionData = await this.oidcService.createSession(userId); const sessionData = await this.oidcService.createSession(userId);
sessionData.cookiesForms.forEach((cookie) => { sessionData.cookiesForms.forEach((cookie) => {
@ -102,7 +107,7 @@ export class AuthService {
}); });
return { return {
status: 'success', status: LoginStatusResponse.SUCCESS,
message: 'Login Successful', message: 'Login Successful',
sessionId: sessionData.sessionId, sessionId: sessionData.sessionId,
}; };
@ -114,11 +119,19 @@ export class AuthService {
* @param userId The User's ID * @param userId The User's ID
* @return * @return
*/ */
public async loginExistingInteraction(req: Request, res: Response, userId: string) { public async loginExistingInteraction(
req: Request,
res: Response,
userId: string,
): Promise<LoginResponse> {
const redirectUrl = await this.interactionService.login(req, res, userId, true); const redirectUrl = await this.interactionService.login(req, res, userId, true);
if (!redirectUrl) {
throw new InternalServerErrorException('No redirect URL after building interaction');
}
return { return {
status: 'redirect', status: LoginStatusResponse.REDIRECT,
message: 'Login Successful, Redirect to session page', message: 'Login Successful, Redirect to session page',
redirectUrl, redirectUrl,
}; };

View file

@ -10,4 +10,4 @@ import { DATABASE_ENTITIES } from 'src/database/database.entities';
providers: [], providers: [],
exports: [], exports: [],
}) })
export class UserModule {} export class ClientModule {}

View file

@ -0,0 +1,6 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
@Controller('organization')
@ApiTags('Clients')
export class ClientsController {}

View file

View file

@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { OidcClient } from '../../database/models/oidc_client.model';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class ClientService {
constructor(
@InjectRepository(OidcClient)
private readonly clientRepository: Repository<OidcClient>,
) {}
public async getClients() {}
}

View file

@ -0,0 +1,59 @@
import { Controller, Get, Render, UseGuards } from '@nestjs/common';
import { ApiExcludeController, ApiExcludeEndpoint } from '@nestjs/swagger';
import { LoginGuard } from '../../auth/guard/login.guard';
import { User } from '../../auth/decorators/user.decorator';
@ApiExcludeController()
@Controller()
export class ViewController {
@Get('auth/login')
@Render('auth/login')
@ApiExcludeEndpoint()
@UseGuards(LoginGuard)
public async loginView(): Promise<any> {
return {
forgot_password: 'forgot-password',
register: 'register',
login_url: '/auth/login',
//background_image: 'https://waterwolf.club/static/img/portal/portal7.jpg',
};
}
@Get('auth/login/totp')
@Render('auth/login-totp')
@ApiExcludeEndpoint()
@UseGuards(LoginGuard)
public async getLoginTotp(): Promise<any> {
return {
login: 'login',
methods: ['authenticator', 'email'],
};
}
@Get('auth/register')
@Render('auth/register')
@ApiExcludeEndpoint()
@UseGuards(LoginGuard)
public async getRegister(): Promise<any> {
return {
login: 'login',
};
}
@Get('auth/forgot-password')
@UseGuards(LoginGuard)
@Render('auth/forgot-password')
@ApiExcludeEndpoint()
public async getForgotPassword(): Promise<any> {
return {
login: 'login',
};
}
@Get('auth/auth-test')
@ApiExcludeEndpoint()
public async getAuthTest(@User() user: any): Promise<any> {
return user;
}
}

10
src/view/view.module.ts Normal file
View file

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ViewController } from './controllers/view.controller';
@Module({
imports: [],
controllers: [ViewController],
providers: [],
exports: [],
})
export class ViewModule {}