migration to typescript
This commit is contained in:
parent
924000b084
commit
9fde77468a
41 changed files with 11952 additions and 10164 deletions
|
|
@ -1,152 +0,0 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
const prisma = require('../config/database');
|
||||
const { generateAccessToken, generateRefreshToken } = require('../utils/jwt');
|
||||
|
||||
class AuthService {
|
||||
/**
|
||||
* Register a new user
|
||||
* @param {Object} data - User registration data
|
||||
* @returns {Promise<Object>} User and tokens
|
||||
*/
|
||||
async register({ username, email, password, role_id = 3 }) {
|
||||
// Check if user exists
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ username }, { email }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
const field = existingUser.username === username ? 'username' : 'email';
|
||||
throw Object.assign(new Error(`This ${field} is already taken`), {
|
||||
statusCode: 409,
|
||||
code: 'DUPLICATE_ENTRY',
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
role_id,
|
||||
},
|
||||
include: {
|
||||
role: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = generateAccessToken({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role.code,
|
||||
});
|
||||
|
||||
const refreshToken = generateRefreshToken({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
// Remove password from response
|
||||
delete user.password;
|
||||
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Login user
|
||||
* @param {Object} credentials - Login credentials
|
||||
* @returns {Promise<Object>} User and tokens
|
||||
*/
|
||||
async login({ username, email, password }) {
|
||||
// Find user
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ username }, { email }],
|
||||
},
|
||||
include: {
|
||||
role: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw Object.assign(new Error('Invalid credentials'), {
|
||||
statusCode: 401,
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!user.is_active) {
|
||||
throw Object.assign(new Error('Account is inactive'), {
|
||||
statusCode: 403,
|
||||
code: 'ACCOUNT_INACTIVE',
|
||||
});
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw Object.assign(new Error('Invalid credentials'), {
|
||||
statusCode: 401,
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
});
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = generateAccessToken({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role.code,
|
||||
});
|
||||
|
||||
const refreshToken = generateRefreshToken({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
// Remove password from response
|
||||
delete user.password;
|
||||
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
* @param {number} userId - User ID
|
||||
* @returns {Promise<Object>} User profile
|
||||
*/
|
||||
async getProfile(userId) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
role: true,
|
||||
profile: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw Object.assign(new Error('User not found'), {
|
||||
statusCode: 404,
|
||||
code: 'NOT_FOUND',
|
||||
});
|
||||
}
|
||||
|
||||
delete user.password;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuthService();
|
||||
217
Backend/src/services/auth.service.ts
Normal file
217
Backend/src/services/auth.service.ts
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue