fix service
All checks were successful
Deploy Production / deploy (push) Successful in 54s

This commit is contained in:
parent 6a4acfd7a4
commit febe51db9c

View File

@ -1,9 +1,13 @@
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'; import {
import { ConfigService } from '@nestjs/config'; Injectable,
import { JwtService } from '@nestjs/jwt'; UnauthorizedException,
import { UsersService } from '../users/users.service'; BadRequestException,
import { LoginDto, ChangePasswordDto, SetupPasswordDto } from './dto'; } from "@nestjs/common";
import { TokenPair } from './interfaces'; import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { UsersService } from "../users/users.service";
import { LoginDto, ChangePasswordDto, SetupPasswordDto } from "./dto";
import { TokenPair } from "./interfaces";
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -13,44 +17,55 @@ export class AuthService {
private usersService: UsersService, private usersService: UsersService,
) {} ) {}
async validateUser(loginDto: LoginDto): Promise<{ id: string; username: string }> { async validateUser(
loginDto: LoginDto,
): Promise<{ id: string; username: string }> {
const { username, password } = loginDto; const { username, password } = loginDto;
const user = await this.usersService.findByUsername(username); const user = await this.usersService.findByUsername(username);
if (!user) { if (!user) {
throw new UnauthorizedException('Invalid credentials'); throw new UnauthorizedException("Invalid credentials");
} }
if (!user.isPasswordSet) { if (!user.isPasswordSet) {
throw new BadRequestException('Password not set. Please use /auth/setup endpoint first.'); throw new BadRequestException(
"Password not set. Please use /auth/setup endpoint first.",
);
} }
const isPasswordValid = await this.usersService.validatePassword(user, password); const isPasswordValid = await this.usersService.validatePassword(
user,
password,
);
if (!isPasswordValid) { if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials'); throw new UnauthorizedException("Invalid credentials");
} }
if (!user.isAdmin) { if (!user.isAdmin) {
throw new UnauthorizedException('Access denied'); throw new UnauthorizedException("Access denied");
} }
return { id: user.id, username: user.username }; return { id: user.id, username: user.username };
} }
async setupPassword(setupPasswordDto: SetupPasswordDto): Promise<{ id: string; username: string }> { async setupPassword(
setupPasswordDto: SetupPasswordDto,
): Promise<{ id: string; username: string }> {
const { username, password } = setupPasswordDto; const { username, password } = setupPasswordDto;
const user = await this.usersService.findByUsername(username); const user = await this.usersService.findByUsername(username);
if (!user) { if (!user) {
throw new UnauthorizedException('User not found'); throw new UnauthorizedException("User not found");
} }
if (!user.isAdmin) { if (!user.isAdmin) {
throw new UnauthorizedException('Access denied'); throw new UnauthorizedException("Access denied");
} }
if (user.isPasswordSet) { if (user.isPasswordSet) {
throw new BadRequestException('Password already set. Use /auth/change-password to change it.'); throw new BadRequestException(
"Password already set. Use /auth/change-password to change it.",
);
} }
await this.usersService.setupPassword(username, password); await this.usersService.setupPassword(username, password);
@ -65,26 +80,29 @@ export class AuthService {
const accessPayload = { const accessPayload = {
sub: userId, sub: userId,
username, username,
type: 'access', type: "access",
}; };
const refreshPayload = { const refreshPayload = {
sub: userId, sub: userId,
username, username,
type: 'refresh', type: "refresh",
}; };
const accessSecret = this.configService.get<string>('JWT_ACCESS_SECRET') || 'default-secret'; const accessSecret =
const refreshSecret = this.configService.get<string>('JWT_REFRESH_SECRET') || 'default-refresh-secret'; this.configService.get<string>("JWT_ACCESS_SECRET") || "default-secret";
const refreshSecret =
this.configService.get<string>("JWT_REFRESH_SECRET") ||
"default-refresh-secret";
const [accessToken, refreshToken] = await Promise.all([ const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(accessPayload, { this.jwtService.signAsync(accessPayload, {
secret: accessSecret, secret: accessSecret,
expiresIn: '15m', expiresIn: "15m",
}), }),
this.jwtService.signAsync(refreshPayload, { this.jwtService.signAsync(refreshPayload, {
secret: refreshSecret, secret: refreshSecret,
expiresIn: '7d', expiresIn: "7d",
}), }),
]); ]);
@ -92,51 +110,60 @@ export class AuthService {
} }
async refreshTokens(userId: string, username: string): Promise<TokenPair> { async refreshTokens(userId: string, username: string): Promise<TokenPair> {
// Verify user still exists and is admin
const user = await this.usersService.findById(userId); const user = await this.usersService.findById(userId);
if (!user || !user.isAdmin) { if (!user || !user.isAdmin) {
throw new UnauthorizedException('User not found or no longer authorized'); throw new UnauthorizedException("User not found or no longer authorized");
} }
return this.generateTokens(userId, username); return this.generateTokens(userId, username);
} }
getCookieOptions(isRefreshToken = false) { getCookieOptions(isRefreshToken = false) {
const isProduction = this.configService.get<string>('NODE_ENV') === 'production'; const isProduction =
const cookieSecure = this.configService.get<string>('COOKIE_SECURE') === 'true'; this.configService.get<string>("NODE_ENV") === "production";
const cookieSecure =
this.configService.get<string>("COOKIE_SECURE") === "true";
const domain = this.configService.get<string>("COOKIE_DOMAIN");
return { return {
httpOnly: true, httpOnly: true,
secure: isProduction || cookieSecure, secure: isProduction || cookieSecure,
sameSite: 'lax' as const, sameSite: "none" as const,
path: isRefreshToken ? '/auth/refresh' : '/', path: isRefreshToken ? "/auth/refresh" : "/",
maxAge: isRefreshToken domain: domain,
? 7 * 24 * 60 * 60 * 1000 // 7 days for refresh token maxAge: isRefreshToken ? 7 * 24 * 60 * 60 * 1000 : 15 * 60 * 1000,
: 15 * 60 * 1000, // 15 minutes for access token
}; };
} }
getClearCookieOptions(isRefreshToken = false) { getClearCookieOptions(isRefreshToken = false) {
return { return {
httpOnly: true, httpOnly: true,
path: isRefreshToken ? '/auth/refresh' : '/', path: isRefreshToken ? "/auth/refresh" : "/",
}; };
} }
async changePassword(userId: string, changePasswordDto: ChangePasswordDto): Promise<void> { async changePassword(
userId: string,
changePasswordDto: ChangePasswordDto,
): Promise<void> {
const { currentPassword, newPassword } = changePasswordDto; const { currentPassword, newPassword } = changePasswordDto;
const user = await this.usersService.findById(userId); const user = await this.usersService.findById(userId);
if (!user) { if (!user) {
throw new UnauthorizedException('User not found'); throw new UnauthorizedException("User not found");
} }
const isCurrentPasswordValid = await this.usersService.validatePassword(user, currentPassword); const isCurrentPasswordValid = await this.usersService.validatePassword(
user,
currentPassword,
);
if (!isCurrentPasswordValid) { if (!isCurrentPasswordValid) {
throw new BadRequestException('Current password is incorrect'); throw new BadRequestException("Current password is incorrect");
} }
if (currentPassword === newPassword) { if (currentPassword === newPassword) {
throw new BadRequestException('New password must be different from current password'); throw new BadRequestException(
"New password must be different from current password",
);
} }
await this.usersService.updatePassword(userId, newPassword); await this.usersService.updatePassword(userId, newPassword);