This commit is contained in:
parent
6a4acfd7a4
commit
febe51db9c
@ -1,9 +1,13 @@
|
||||
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common';
|
||||
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';
|
||||
import {
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
BadRequestException,
|
||||
} from "@nestjs/common";
|
||||
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()
|
||||
export class AuthService {
|
||||
@ -13,44 +17,55 @@ export class AuthService {
|
||||
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 user = await this.usersService.findByUsername(username);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
throw new UnauthorizedException("Invalid credentials");
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
throw new UnauthorizedException("Invalid credentials");
|
||||
}
|
||||
|
||||
if (!user.isAdmin) {
|
||||
throw new UnauthorizedException('Access denied');
|
||||
throw new UnauthorizedException("Access denied");
|
||||
}
|
||||
|
||||
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 user = await this.usersService.findByUsername(username);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('User not found');
|
||||
throw new UnauthorizedException("User not found");
|
||||
}
|
||||
|
||||
if (!user.isAdmin) {
|
||||
throw new UnauthorizedException('Access denied');
|
||||
throw new UnauthorizedException("Access denied");
|
||||
}
|
||||
|
||||
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);
|
||||
@ -65,26 +80,29 @@ export class AuthService {
|
||||
const accessPayload = {
|
||||
sub: userId,
|
||||
username,
|
||||
type: 'access',
|
||||
type: "access",
|
||||
};
|
||||
|
||||
const refreshPayload = {
|
||||
sub: userId,
|
||||
username,
|
||||
type: 'refresh',
|
||||
type: "refresh",
|
||||
};
|
||||
|
||||
const accessSecret = this.configService.get<string>('JWT_ACCESS_SECRET') || 'default-secret';
|
||||
const refreshSecret = this.configService.get<string>('JWT_REFRESH_SECRET') || 'default-refresh-secret';
|
||||
const accessSecret =
|
||||
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([
|
||||
this.jwtService.signAsync(accessPayload, {
|
||||
secret: accessSecret,
|
||||
expiresIn: '15m',
|
||||
expiresIn: "15m",
|
||||
}),
|
||||
this.jwtService.signAsync(refreshPayload, {
|
||||
secret: refreshSecret,
|
||||
expiresIn: '7d',
|
||||
expiresIn: "7d",
|
||||
}),
|
||||
]);
|
||||
|
||||
@ -92,51 +110,60 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async refreshTokens(userId: string, username: string): Promise<TokenPair> {
|
||||
// Verify user still exists and is admin
|
||||
const user = await this.usersService.findById(userId);
|
||||
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);
|
||||
}
|
||||
|
||||
getCookieOptions(isRefreshToken = false) {
|
||||
const isProduction = this.configService.get<string>('NODE_ENV') === 'production';
|
||||
const cookieSecure = this.configService.get<string>('COOKIE_SECURE') === 'true';
|
||||
const isProduction =
|
||||
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 {
|
||||
httpOnly: true,
|
||||
secure: isProduction || cookieSecure,
|
||||
sameSite: 'lax' as const,
|
||||
path: isRefreshToken ? '/auth/refresh' : '/',
|
||||
maxAge: isRefreshToken
|
||||
? 7 * 24 * 60 * 60 * 1000 // 7 days for refresh token
|
||||
: 15 * 60 * 1000, // 15 minutes for access token
|
||||
sameSite: "none" as const,
|
||||
path: isRefreshToken ? "/auth/refresh" : "/",
|
||||
domain: domain,
|
||||
maxAge: isRefreshToken ? 7 * 24 * 60 * 60 * 1000 : 15 * 60 * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
getClearCookieOptions(isRefreshToken = false) {
|
||||
return {
|
||||
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 user = await this.usersService.findById(userId);
|
||||
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) {
|
||||
throw new BadRequestException('Current password is incorrect');
|
||||
throw new BadRequestException("Current password is incorrect");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user