initial commit

This commit is contained in:
2024-07-19 19:55:57 +02:00
commit 049e063d48
53 changed files with 15239 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,35 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
Request,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { Role } from 'src/users/roles/role.enum';
import { Auth } from './auth.decorator';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Auth(Role.Public)
@HttpCode(HttpStatus.OK)
@Post('login')
signIn(@Body() signInDto: Record<string, any>) {
return this.authService.signIn(signInDto.username, signInDto.password);
}
@Auth(Role.Admin)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
@Get('options')
getOptions(@Request() req) {
return req.roles;
}
}

View File

@@ -0,0 +1,8 @@
import { SetMetadata, applyDecorators } from '@nestjs/common';
import { Role } from 'src/users/roles/role.enum';
export const ROLES_METADATA = 'roles';
export function Auth(...roles: Role[]) {
return applyDecorators(SetMetadata(ROLES_METADATA, roles));
}

View File

@@ -0,0 +1,60 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { Request } from 'express';
import { Reflector } from '@nestjs/core';
import { ROLES_METADATA } from './auth.decorator';
import { Role } from 'src/users/roles/role.enum';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const decorator_roles = this.reflector.getAllAndOverride<Role[]>(
ROLES_METADATA,
[context.getHandler(), context.getClass()],
);
if (decorator_roles && decorator_roles.includes(Role.Public)) {
return true;
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: jwtConstants.secret,
});
// 💡 We're assigning the payload to the request object here
// so that we can access it in our route handlers
request['user'] = payload;
const found = payload.roles.some((r: Role) =>
decorator_roles.includes(r),
);
if (!found) {
throw new UnauthorizedException();
}
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}

View File

@@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { jwtConstants } from './constants';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
//TODO: TIMING LOGIC: ID Token: 60 minutes. Access Token: 60 minutes. Refresh Token: 90 days
@Module({
imports: [
UsersModule,
JwtModule.register({
global: true,
secret: jwtConstants.secret,
signOptions: { expiresIn: '60m' },
}),
],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,32 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from 'src/users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async signIn(
username: string,
pass: string,
): Promise<{ access_token: string }> {
console.log(username, pass);
const user = await this.usersService.findOne(username);
if (!user) throw new UnauthorizedException();
const isSamePasswd = await bcrypt.compare(`${pass}`, `${user?.password}`);
if (!isSamePasswd) throw new UnauthorizedException();
const payload = {
sub: user.id,
username: user.username,
roles: user.roles,
};
return { access_token: await this.jwtService.signAsync(payload) };
}
}

View File

@@ -0,0 +1,5 @@
//TODO: remove this and do it propertly
export const jwtConstants = {
secret:
'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
};