This commit is contained in:
parent
6a4acfd7a4
commit
febe51db9c
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user