mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-18 01:00:38 +01:00
relocate auth module
This commit is contained in:
55
backend/src/managers/auth/auth.module.ts
Normal file
55
backend/src/managers/auth/auth.module.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
64
backend/src/managers/auth/auth.service.ts
Normal file
64
backend/src/managers/auth/auth.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
25
backend/src/managers/auth/guards/admin.guard.ts
Normal file
25
backend/src/managers/auth/guards/admin.guard.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
5
backend/src/managers/auth/guards/jwt.guard.ts
Normal file
5
backend/src/managers/auth/guards/jwt.guard.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||
39
backend/src/managers/auth/guards/jwt.strategy.ts
Normal file
39
backend/src/managers/auth/guards/jwt.strategy.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
5
backend/src/managers/auth/guards/localauth.guard.ts
Normal file
5
backend/src/managers/auth/guards/localauth.guard.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class LocalAuthGuard extends AuthGuard('local') {}
|
||||
22
backend/src/managers/auth/guards/localauth.strategy.ts
Normal file
22
backend/src/managers/auth/guards/localauth.strategy.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user