elearning/Backend/src/services/auth.service.ts

218 lines
6.4 KiB
TypeScript
Raw Normal View History

2026-01-09 06:28:15 +00:00
import { prisma } from '../config/database';
import { Prisma } from '@prisma/client';
import { config } from '../config';
import { logger } from '../config/logger';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import {
LoginRequest,
RegisterRequest,
LoginResponse,
RegisterResponse,
RefreshTokenResponse,
UserResponse
} from '../types/auth.types';
import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
export class AuthService {
/**
* User login
*/
async login(data: LoginRequest): Promise<LoginResponse> {
const { username, password } = data;
// Find user with role and profile
const user = await prisma.user.findUnique({
where: { username },
include: {
role: true,
profile: true
}
});
if (!user) {
logger.warn('Login attempt with invalid username', { username });
throw new UnauthorizedError('Invalid username or password');
}
// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
logger.warn('Login attempt with invalid password', { username });
throw new UnauthorizedError('Invalid username or password');
}
// Generate tokens
const token = this.generateAccessToken(user.id, user.username, user.email, user.role.code);
const refreshToken = this.generateRefreshToken(user.id);
logger.info('User logged in successfully', { userId: user.id, username: user.username });
return {
token,
refreshToken,
user: this.formatUserResponse(user)
};
}
/**
* User registration
*/
async register(data: RegisterRequest): Promise<RegisterResponse> {
const { username, email, password, first_name, last_name, prefix } = data;
// Check if username already exists
const existingUsername = await prisma.user.findUnique({
where: { username }
});
if (existingUsername) {
throw new ValidationError('Username already exists');
}
// Check if email already exists
const existingEmail = await prisma.user.findUnique({
where: { email }
});
if (existingEmail) {
throw new ValidationError('Email already exists');
}
// Get STUDENT role
const studentRole = await prisma.role.findUnique({
where: { code: 'STUDENT' }
});
if (!studentRole) {
logger.error('STUDENT role not found in database');
throw new Error('System configuration error');
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user with profile
const user = await prisma.user.create({
data: {
username,
email,
password: hashedPassword,
role_id: studentRole.id,
profile: {
create: {
prefix: prefix ? prefix as Prisma.InputJsonValue : Prisma.JsonNull,
first_name,
last_name
}
}
},
include: {
role: true,
profile: true
}
});
logger.info('New user registered', { userId: user.id, username: user.username });
return {
user: this.formatUserResponse(user),
message: 'Registration successful'
};
}
/**
* Refresh access token
*/
async refreshToken(refreshToken: string): Promise<RefreshTokenResponse> {
try {
// Verify refresh token
const decoded = jwt.verify(refreshToken, config.jwt.secret) as { id: number; type: string };
if (decoded.type !== 'refresh') {
throw new UnauthorizedError('Invalid token type');
}
// Get user
const user = await prisma.user.findUnique({
where: { id: decoded.id },
include: { role: true }
});
if (!user) {
throw new UnauthorizedError('User not found');
}
// Generate new tokens
const newToken = this.generateAccessToken(user.id, user.username, user.email, user.role.code);
const newRefreshToken = this.generateRefreshToken(user.id);
logger.info('Token refreshed', { userId: user.id });
return {
token: newToken,
refreshToken: newRefreshToken
};
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
throw new UnauthorizedError('Invalid refresh token');
}
if (error instanceof jwt.TokenExpiredError) {
throw new UnauthorizedError('Refresh token expired');
}
throw error;
}
}
/**
* Generate access token (JWT)
*/
private generateAccessToken(id: number, username: string, email: string, roleCode: string): string {
return jwt.sign(
{
id,
username,
email,
roleCode
},
config.jwt.secret,
{ expiresIn: config.jwt.expiresIn } as jwt.SignOptions
);
}
/**
* Generate refresh token
*/
private generateRefreshToken(id: number): string {
return jwt.sign(
{
id,
type: 'refresh'
},
config.jwt.secret,
{ expiresIn: config.jwt.refreshExpiresIn } as jwt.SignOptions
);
}
/**
* Format user response
*/
private formatUserResponse(user: any): UserResponse {
return {
id: user.id,
username: user.username,
email: user.email,
role: {
code: user.role.code,
name: user.role.name
},
profile: user.profile ? {
prefix: user.profile.prefix,
first_name: user.profile.first_name,
last_name: user.profile.last_name,
avatar_url: user.profile.avatar_url
} : undefined
};
}
}