auth checkpoint

auth dependecies not instaled in dev container
This commit is contained in:
dimitar committed 2026-02-04 19:24:03 +01:00
1 parent 7378d37b36
commit 42002f8e6f
50 files changed
+3680 -636

No files matched your search

@@ -0,0 +1,31 @@
import {
Controller,
Post,
Body,
HttpCode,
HttpStatus,
UseGuards,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginUserDto, CreateUserDto } from '../users/user.dto';
import { LocalAuthGuard } from './local-auth.guard';
import { Public } from './public.decorator';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
@HttpCode(HttpStatus.OK)
async login(@Body() loginUserDto: LoginUserDto) {
return this.authService.login(loginUserDto);
}
@Public()
@Post('register')
async register(@Body() createUserDto: CreateUserDto) {
return this.authService.register(createUserDto);
}
}
+34
View File
@@ -0,0 +1,34 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { UserModule } from '../users/user.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
UserModule,
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
const expiration = configService.get<string>('JWT_EXPIRATION') || '7d';
return {
secret:
configService.get<string>('JWT_SECRET') || 'default-secret-key',
signOptions: {
expiresIn: expiration ? parseInt(expiration) : 3600, // Convert to number
},
};
},
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
+80
View File
@@ -0,0 +1,80 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../users/user.service';
import { CreateUserDto, LoginUserDto } from '../users/user.dto';
import { AuthResponse, JwtPayload } from './types';
import { UserRole } from '../entities';
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async validateUser(
username: string,
password: string,
): Promise<{
id: string;
username: string;
email: string;
role: string;
} | null> {
return this.userService.validateUser(username, password);
}
async login(loginUserDto: LoginUserDto): Promise<AuthResponse> {
const user = await this.validateUser(
loginUserDto.username,
loginUserDto.password,
);
if (!user) {
throw new Error('Invalid credentials');
}
const payload: JwtPayload = {
sub: user.id,
username: user.username,
email: user.email,
role: user.role as UserRole,
};
return {
access_token: this.jwtService.sign(payload),
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role as UserRole,
},
};
}
async register(createUserDto: CreateUserDto): Promise<AuthResponse> {
// Default new users to USER role
const userDto = {
...createUserDto,
role: UserRole.USER,
};
const user = await this.userService.create(userDto);
const payload: JwtPayload = {
sub: user.id,
username: user.username,
email: user.email,
role: user.role,
};
return {
access_token: this.jwtService.sign(payload),
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
},
};
}
}
@@ -0,0 +1,24 @@
import { Injectable, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from './public.decorator';
@Injectable()
export class JwtAuthPublicGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
@@ -0,0 +1,9 @@
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
+27
View File
@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { JwtPayload } from './types';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey:
configService.get<string>('JWT_SECRET') || 'default-secret-key',
});
}
async validate(payload: JwtPayload) {
await Promise.resolve(); // Add await to satisfy eslint rule
return {
id: payload.sub,
username: payload.username,
email: payload.email,
role: payload.role,
};
}
}
@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
@@ -0,0 +1,22 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(
username: string,
password: string,
): Promise<{ id: string; username: string; email: string; role: string }> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user;
}
}
@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../entities';
export const Roles = (...roles: UserRole[]) => SetMetadata('roles', roles);
+31
View File
@@ -0,0 +1,31 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { UserRole } from '../entities';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(
'roles',
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const request = context.switchToHttp().getRequest();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const user = request.user as
| { id: string; username: string; email: string; role: UserRole }
| undefined;
if (!user) {
return false;
}
return requiredRoles.includes(user.role);
}
}
+27
View File
@@ -0,0 +1,27 @@
import { UserRole } from '../entities';
export interface JwtPayload {
sub: string;
username: string;
email: string;
role: UserRole;
}
export interface RequestWithUser extends Request {
user: {
id: string;
username: string;
email: string;
role: UserRole;
};
}
export interface AuthResponse {
access_token: string;
user: {
id: string;
username: string;
email: string;
role: UserRole;
};
}