relocate auth module

This commit is contained in:
rubikscraft
2022-03-07 20:19:58 +01:00
parent fd4a5c2293
commit a4854d02ae
14 changed files with 82 additions and 68 deletions

View File

@@ -0,0 +1,55 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { SysPreferenceModule } from '../../collections/syspreferencesdb/syspreferencedb.module';
import { UsersModule } from '../../collections/userdb/userdb.module';
import { AuthConfigService } from '../../config/auth.config.service';
import {
JwtConfigService,
JwtSecretProvider
} from '../../config/jwt.lateconfig.service';
import { PicsurLateConfigModule } from '../../config/lateconfig.module';
import { AuthManagerService } from './auth.service';
import { JwtStrategy } from './guards/jwt.strategy';
import { LocalAuthStrategy } from './guards/localauth.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
SysPreferenceModule,
PicsurLateConfigModule,
JwtModule.registerAsync({
useExisting: JwtConfigService,
imports: [PicsurLateConfigModule],
}),
],
providers: [
AuthManagerService,
LocalAuthStrategy,
JwtStrategy,
JwtSecretProvider,
],
exports: [AuthManagerService],
})
export class AuthManagerModule implements OnModuleInit {
private readonly logger = new Logger('AuthModule');
constructor(
private authService: AuthManagerService,
private authConfigService: AuthConfigService,
) {}
async onModuleInit() {
await this.ensureAdminExists();
}
private async ensureAdminExists() {
const username = this.authConfigService.getDefaultAdminUsername();
const password = this.authConfigService.getDefaultAdminPassword();
this.logger.debug(`Ensuring admin user "${username}" exists`);
await this.authService.createUser(username, password);
await this.authService.makeAdmin(username);
}
}

View File

@@ -0,0 +1,64 @@
import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { instanceToPlain, plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../collections/userdb/userdb.service';
import { EUserBackend } from '../../models/entities/user.entity';
@Injectable()
export class AuthManagerService {
private readonly logger = new Logger('AuthService');
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async createUser(username: string, password: string): AsyncFailable<EUserBackend> {
const hashedPassword = await bcrypt.hash(password, 12);
return this.usersService.create(username, hashedPassword);
}
async deleteUser(user: string | EUserBackend): AsyncFailable<EUserBackend> {
return this.usersService.delete(user);
}
async listUsers(): AsyncFailable<EUserBackend[]> {
return this.usersService.findAll();
}
async authenticate(username: string, password: string): AsyncFailable<EUserBackend> {
const user = await this.usersService.findOne(username, true);
if (HasFailed(user)) return user;
if (!(await bcrypt.compare(password, user.password)))
return Fail('Wrong password');
return await this.usersService.findOne(username);
}
async createToken(user: EUserBackend): Promise<string> {
const jwtData: JwtDataDto = plainToClass(JwtDataDto, {
user,
});
const errors = await validate(jwtData, { forbidUnknownValues: true });
if (errors.length > 0) {
this.logger.warn(errors);
throw new Error('Invalid jwt token generated');
}
return this.jwtService.signAsync(instanceToPlain(jwtData));
}
async makeAdmin(user: string | EUserBackend): AsyncFailable<true> {
return this.usersService.modifyAdmin(user, true);
}
async revokeAdmin(user: string | EUserBackend): AsyncFailable<true> {
return this.usersService.modifyAdmin(user, false);
}
}

View File

@@ -0,0 +1,25 @@
import {
CanActivate,
ExecutionContext, Injectable, Logger
} from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { EUserBackend } from '../../../models/entities/user.entity';
@Injectable()
export class AdminGuard implements CanActivate {
private readonly logger = new Logger('AdminGuard');
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = plainToClass(EUserBackend, request.user);
const errors = await validate(user, {forbidUnknownValues: true});
if (errors.length > 0) {
this.logger.warn(errors);
return false;
}
return user.isAdmin;
}
}

View File

@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

View File

@@ -0,0 +1,39 @@
import {
Inject,
Injectable,
Logger,
UnauthorizedException
} from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
import { EUserBackend } from '../../../models/entities/user.entity';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
private readonly logger = new Logger('JwtStrategy');
constructor(@Inject('JWT_SECRET') private jwtSecret: string) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtSecret,
});
}
async validate(payload: any): Promise<EUserBackend> {
const jwt = plainToClass(JwtDataDto, payload);
const errors = await validate(jwt, {
forbidUnknownValues: true,
});
if (errors.length > 0) {
this.logger.warn(errors);
throw new UnauthorizedException();
}
return jwt.user;
}
}

View File

@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

View File

@@ -0,0 +1,22 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { EUserBackend } from '../../../models/entities/user.entity';
import { AuthManagerService } from '../auth.service';
@Injectable()
export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') {
constructor(private authService: AuthManagerService) {
super();
}
async validate(username: string, password: string): AsyncFailable<EUserBackend> {
const user = await this.authService.authenticate(username, password);
if (HasFailed(user)) {
throw new UnauthorizedException();
}
return user;
}
}