Skip to content

Commit

Permalink
redesign auth stuff
Browse files Browse the repository at this point in the history
dotFionn committed Jun 29, 2024
1 parent f4e6392 commit 9fa1fd9
Showing 9 changed files with 95 additions and 26 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
"test": "jest",
"paths": "resolve-tspaths --out \"dist\"",
"start": "nest start",
"dev": "nest start --debug --watch",
"dev": "nest start --debug=0.0.0.0 --watch",
"spa-dev": "vite --config=src/frontend/vite.config.ts --host",
"spa-build": "vite --config=src/frontend/vite.config.ts build",
"spa-preview": "vite --config=src/frontend/vite.config.ts preview"
7 changes: 3 additions & 4 deletions src/backend/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller, Get, Post, Query, Res, UseGuards } from '@nestjs/common';
import { Controller, Get, Post, Query, Res } from '@nestjs/common';
import { ApiExcludeController } from '@nestjs/swagger';
import { Response } from 'express';
import Joi from 'joi';
@@ -7,8 +7,7 @@ import { JoiPipe } from 'nestjs-joi';
import logger from '../logger';
import { UserDocument } from '../user/user.model';

import { User } from './auth.decorator';
import { AuthGuard } from './auth.guard';
import { Auth, User } from './auth.decorator';
import { AuthService } from './auth.service';

export const COOKIE_NAME_VACDM_TOKEN = 'vacdm_token';
@@ -48,7 +47,7 @@ export class AuthController {
}

@Get('/profile')
@UseGuards(AuthGuard)
@Auth('any')
getProfile(@User() user: UserDocument) {
return user;
}
29 changes: 24 additions & 5 deletions src/backend/auth/auth.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
import { ExecutionContext, SetMetadata, UseGuards, applyDecorators, createParamDecorator } from '@nestjs/common';
import { Request } from 'express';

import { UserDocument } from '../user/user.model';

export const User = createParamDecorator(
function getRequestUserDecorator(data: void, ctx: ExecutionContext): UserDocument {
const request = ctx.switchToHttp().getRequest();
import { AuthGuard } from './auth.guard';

return request.user;
export type AcceptedUserTypes = 'web' | 'plugin' | 'any';

export function Auth(usertype: AcceptedUserTypes = 'any') {
return applyDecorators(
SetMetadata('usertype', usertype),
UseGuards(AuthGuard),
);
}

export const User = createParamDecorator<AcceptedUserTypes | undefined>(
function getRequestUserDecorator(data: AcceptedUserTypes | undefined, ctx: ExecutionContext): UserDocument | undefined {
const request = ctx.switchToHttp().getRequest<Request>();

if (data === 'web') {
return request.webUser;
}
if (data === 'plugin') {
return request.pluginUser;
}

return request.user || request.webUser || request.pluginUser;
},
);
23 changes: 21 additions & 2 deletions src/backend/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -2,15 +2,34 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';

import { AcceptedUserTypes } from './auth.decorator';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();

const { user } = request;
const usertype = this.reflector.get<AcceptedUserTypes>('usertype', context.getHandler()) || 'any';

switch (usertype) {
case 'web': {
request.user = request.webUser;
break;
}
case 'plugin': {
request.user = request.pluginUser;
break;
}
case 'any': {
request.user = request.webUser || request.pluginUser;
break;
}
}



return !!user;
return !!request.user;
}
}
28 changes: 21 additions & 7 deletions src/backend/auth/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -9,15 +9,29 @@ import { COOKIE_NAME_VACDM_TOKEN } from './auth.controller';
export class AuthMiddleware implements NestMiddleware {
constructor(private userService: UserService) {}

async use(request: Request, response: Response, next: NextFunction) {
const token = request.cookies[COOKIE_NAME_VACDM_TOKEN];
private headerTokenRegex = /^Bearer (.+)$/;

if (!token) return next();
async use(request: Request, response: Response, next: NextFunction) {
const cookieToken = request.cookies[COOKIE_NAME_VACDM_TOKEN];

try {
request.user = await this.userService.getUserFromToken(token);
} catch (_) {
// do nothing
if (cookieToken) {
try {
request.webUser = await this.userService.getUserFromToken(cookieToken);
} catch (_) {
// do nothing
}
}

const pluginToken = request.headers.authorization;

const res = this.headerTokenRegex.exec(pluginToken || '');

if (res) {
try {
request.pluginUser = await this.userService.getUserFromPluginToken(res[1]);
} catch (_) {
// do nothing
}
}

next();
6 changes: 4 additions & 2 deletions src/backend/custom.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import User from '@/shared/interfaces/user.interface';
import { UserDocument } from './user/user.model';

declare global {
namespace Express {
interface Request {
user?: User;
webUser?: UserDocument;
pluginUser?: UserDocument;
user?: UserDocument;
}
}
}
10 changes: 5 additions & 5 deletions src/backend/plugin-token/plugin-token.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, NotFoundException, Param, Post, Query, Res, UnauthorizedException, UseGuards } from '@nestjs/common';
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, NotFoundException, Param, Post, Query, Res, UnauthorizedException } from '@nestjs/common';
import { ApiExcludeController } from '@nestjs/swagger';
import { Response } from 'express';
import { FilterQuery } from 'mongoose';

import PluginToken from '../../shared/interfaces/plugin-token.interface';
import { User } from '../auth/auth.decorator';
import { AuthGuard } from '../auth/auth.guard';
import { Auth, User } from '../auth/auth.decorator';
import getAppConfig from '../config';
import logger from '../logger';
import { UserDocument } from '../user/user.model';
@@ -40,6 +39,7 @@ export class PluginTokenController {
*/
@HttpCode(200)
@Post('/authorize/:id')
@Auth('web')
async authorizePluginToken(@Param('id') tokenId: string, @User() user: UserDocument, @Body('confirm') confirmation: string, @Body('label') label: string) {
if (!user) {
throw new UnauthorizedException();
@@ -101,7 +101,7 @@ export class PluginTokenController {
}

@Get('')
@UseGuards(AuthGuard)
@Auth('web')
async getAllTokens(@User() user: UserDocument, @Query('scope') scope = 'own') {
let filter: FilterQuery<PluginToken> = { user: user._id };

@@ -115,7 +115,7 @@ export class PluginTokenController {
}

@Delete('/:id')
@UseGuards(AuthGuard)
@Auth('web')
async revokeToken(@User() user: UserDocument, @Param('id') id: string) {
const token = await this.pluginTokenService.findToken({ user: user._id, _id: id });

2 changes: 2 additions & 0 deletions src/backend/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';

import { DatabaseModule } from '../database.module';
import { PluginTokenModule } from '../plugin-token/plugin-token.module';
import { ScheduleModule } from '../schedule.module';

import { UserController } from './user.controller';
@@ -11,6 +12,7 @@ import { UserService } from './user.service';
imports: [
DatabaseModule,
ScheduleModule,
PluginTokenModule,
],
providers: [UserService, UserProvider],
exports: [UserService],
14 changes: 14 additions & 0 deletions src/backend/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { FilterQuery } from 'mongoose';

import getAppConfig from '../config';
import logger from '../logger';
import { PluginTokenService } from '../plugin-token/plugin-token.service';
import { AGENDA_PROVIDER } from '../schedule.module';

import { USER_MODEL, UserDocument, UserModel } from './user.model';
@@ -17,6 +18,7 @@ export class UserService {
constructor(
@Inject(USER_MODEL) private userModel: UserModel,
@Inject(AGENDA_PROVIDER) private agenda: Agenda,
private pluginTokenService: PluginTokenService,
) {
this.agenda.define('USER_cleanupUsers', this.cleanupUsers.bind(this));
this.agenda.every('10 minutes', 'USER_cleanupUsers');
@@ -94,6 +96,18 @@ export class UserService {
return this.getUserFromCid(tokenData.cid);
}

async getUserFromPluginToken(token: string): Promise<UserDocument> {
const userId = await this.pluginTokenService.attemptPluginAuthentication(token);

if (typeof userId === 'undefined') {
throw new NotFoundException();
}

const user = await this.getUserFromId(userId);

return user;
}

async cleanupUsers() {
const inactiveUsers = await this.getUsers({
admin: false,

0 comments on commit 9fa1fd9

Please sign in to comment.