123 lines
3.9 KiB
TypeScript

import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { Pool } from 'mysql2/promise';
import logger from '../utils/logger';
import { UserService } from '../services';
import { User, UserSession, CreateUserSessionDTO, UpdateUser, UserWithSession } from '../types/user';
export class AuthService {
private userService: UserService;
constructor(pool: Pool) {
this.userService = new UserService(pool);
}
// Generate JWT token
generateAccessToken(user: Partial<User>) {
return jwt.sign(
{ userId: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET as string,
{ expiresIn: '15m' }
);
};
// Generate JWT tokens
generateTokens(user: Partial<User>) {
const accessToken = jwt.sign(
{ userId: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET as string,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id, username: user.username, role: user.role, type: 'refresh' }, // Include a claim to distinguish token types
process.env.JWT_SECRET as string,
{ expiresIn: '7d' } // Longer expiry for refresh token
);
const sessionToken = crypto.randomBytes(40).toString('hex');
return { accessToken, refreshToken, sessionToken };
}
// Validate the user by username and password
async validateUser(username: string, password: string): Promise<User> {
const user = await this.userService.getUserByUsername(username);
if (!user) {
throw new Error('Invalid credentials'); // Throw custom error
}
const isValid = await bcrypt.compare(password, user.password_hash);
if (!isValid) {
throw new Error('Invalid credentials'); // Throw custom error
}
return user; // Return the valid user
};
async getUserById (id: number): Promise<User | null> {
return await this.userService.getUserById(id);
};
async createUserSession (userSessionData: Partial<UserSession>) {
const requiredFields = [
'user_id',
'session_token',
'refresh_token',
'ip_address',
'user_agent',
'expires_at'
];
for (const field of requiredFields) {
// Check for undefined or null only (allow empty strings)
if (userSessionData[field as keyof UserSession] == null) {
throw new Error(`Missing required field: ${field}`);
}
}
logger.info('Creating new user session', { userSessionData });
const userSession = await this.userService.createUserSession(userSessionData);
};
async updateUser (userId: number, user: UpdateUser) {
this.userService.updateUser(userId, user);
};
async getUserSessionByIp (userId: number, ipAdress: string): Promise<UserSession | null> {
return await this.userService.getUserSessionByIp(userId, ipAdress);
};
async getUserSessionByUserAndAgent (userId: number, userAgent: string): Promise<UserSession | null> {
return await this.userService.getUserSessionByUserAndAgent(userId, userAgent);
};
async getUserAndSessionByRefreshToken (refreshToken: string): Promise<User | null> {
return this.userService.getUserAndSessionByRefreshToken(refreshToken);
};
async deleteUserSession (refreshToken: string) {
this.userService.deleteUserSession(refreshToken);
};
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
const requiredFields = [
'session_token',
'refresh_token',
'expires_at'
];
for (const field of requiredFields) {
// Check for undefined or null only (allow empty strings)
if (userSessionData[field as keyof UserSession] == null) {
throw new Error(`Missing required field: ${field}`);
}
}
logger.info('Updating user session', { userSessionData });
const userSession = await this.userService.updateUserSession(refreshToken, userSessionData);
};
}