fix login

This commit is contained in:
yotakii
2026-03-05 12:16:29 +03:00
parent 625bc26a05
commit 6d82c5007c
2 changed files with 133 additions and 177 deletions

View File

@@ -1,6 +1,6 @@
import { Request, Response } from 'express'
import { authService } from './auth.service'
import { AuthRequest } from '@/shared/middleware/auth'
import { AuthRequest } from '../../shared/middleware/auth'
export const authController = {
register: async (req: Request, res: Response) => {
@@ -31,7 +31,7 @@ export const authController = {
})
}
const result = await authService.login(email, password)
const result = await authService.login(String(email).trim(), String(password))
res.status(200).json({
success: true,
@@ -39,9 +39,9 @@ export const authController = {
data: result
})
} catch (error: any) {
res.status(401).json({
res.status(error?.statusCode || 401).json({
success: false,
message: error.message
message: error.message || 'بيانات الدخول غير صحيحة'
})
}
},

View File

@@ -1,26 +1,27 @@
import bcrypt from 'bcryptjs';
import jwt, { Secret, SignOptions } from 'jsonwebtoken';
import prisma from '../../config/database';
import { config } from '../../config';
import { AppError } from '../../shared/middleware/errorHandler';
import bcrypt from 'bcryptjs'
import jwt, { Secret, SignOptions } from 'jsonwebtoken'
import prisma from '../../config/database'
import { config } from '../../config'
import { AppError } from '../../shared/middleware/errorHandler'
class AuthService {
async register(data: {
email: string;
username: string;
password: string;
employeeId?: string;
email: string
username: string
password: string
employeeId?: string
}) {
// Hash password
const hashedPassword = await bcrypt.hash(data.password, config.security.bcryptRounds);
const hashedPassword = await bcrypt.hash(
data.password,
config.security.bcryptRounds
)
// Create user
const user = await prisma.user.create({
data: {
email: data.email,
username: data.username,
password: hashedPassword,
employeeId: data.employeeId,
employeeId: data.employeeId
},
select: {
id: true,
@@ -28,154 +29,140 @@ class AuthService {
username: true,
employeeId: true,
isActive: true,
createdAt: true,
},
});
createdAt: true
}
})
// Generate tokens
const tokens = this.generateTokens(user.id, user.email);
const tokens = this.generateTokens(user.id, user.email)
// Save refresh token
await prisma.user.update({
where: { id: user.id },
data: { refreshToken: tokens.refreshToken },
});
data: { refreshToken: tokens.refreshToken }
})
return {
user,
...tokens,
};
...tokens
}
}
async login(email: string, password: string) {
const identifier = (email || '').toString().trim();
const isEmail = identifier.includes('@');
async login(emailOrUsername: string, password: string) {
const identifier = (emailOrUsername || '').trim()
const isEmail = identifier.includes('@')
let user: any = null;
let user: any = null
if (isEmail) {
// email may be duplicated => use findMany and validate
const users = await prisma.user.findMany({
const matches = await prisma.user.findMany({
where: { email: identifier },
include: {
employee: {
include: {
position: {
include: { permissions: true },
},
department: true,
},
},
},
});
position: { include: { permissions: true } },
department: true
}
}
}
})
if (users.length === 0) {
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials');
if (matches.length === 0) {
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials')
}
if (users.length > 1) {
if (matches.length > 1) {
throw new AppError(
400,
'هذا البريد مستخدم لأكثر من حساب. الرجاء تسجيل الدخول باسم المستخدم - Email shared, use username'
);
'هذا البريد مستخدم لأكثر من حساب. الرجاء تسجيل الدخول باسم المستخدم - Use username'
)
}
user = users[0];
user = matches[0]
} else {
// username is unique => findUnique OK
user = await prisma.user.findUnique({
where: { username: identifier },
include: {
employee: {
include: {
position: {
include: { permissions: true },
},
department: true,
},
},
},
});
position: { include: { permissions: true } },
department: true
}
}
}
})
if (!user) {
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials');
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials')
}
}
// Check if user is active
if (!user.isActive) {
throw new AppError(403, 'الحساب غير مفعل - Account is inactive');
throw new AppError(403, 'الحساب غير مفعل - Account is inactive')
}
// Check if account is locked
if (user.lockedUntil && user.lockedUntil > new Date()) {
throw new AppError(403, 'الحساب مقفل مؤقتاً - Account is temporarily locked');
throw new AppError(403, 'الحساب مقفل مؤقتاً - Account is temporarily locked')
}
// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
const isPasswordValid = await bcrypt.compare(password, user.password)
if (!isPasswordValid) {
// Increment failed login attempts
const failedAttempts = (user.failedLoginAttempts || 0) + 1;
const updateData: any = { failedLoginAttempts: failedAttempts };
const failedAttempts = (user.failedLoginAttempts || 0) + 1
const updateData: any = { failedLoginAttempts: failedAttempts }
// Lock account after 5 failed attempts
if (failedAttempts >= 5) {
updateData.lockedUntil = new Date(Date.now() + 30 * 60 * 1000); // Lock for 30 minutes
updateData.lockedUntil = new Date(Date.now() + 30 * 60 * 1000)
}
await prisma.user.update({
where: { id: user.id },
data: updateData,
});
data: updateData
})
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials');
throw new AppError(401, 'بيانات الدخول غير صحيحة - Invalid credentials')
}
// Check HR requirement: Must have active employee record
if (!user.employee || user.employee.status !== 'ACTIVE') {
throw new AppError(403, 'الوصول مرفوض - Access denied. Active employee record required.');
throw new AppError(
403,
'الوصول مرفوض - Access denied. Active employee record required.'
)
}
// Reset failed attempts
await prisma.user.update({
where: { id: user.id },
data: {
failedLoginAttempts: 0,
lockedUntil: null,
lastLogin: new Date(),
},
});
lastLogin: new Date()
}
})
// Generate tokens
const tokens = this.generateTokens(user.id, user.email);
const tokens = this.generateTokens(user.id, user.email)
// Save refresh token
await prisma.user.update({
where: { id: user.id },
data: { refreshToken: tokens.refreshToken },
});
data: { refreshToken: tokens.refreshToken }
})
// Return user data without password, with role info
const { password: _, ...userWithoutPassword } = user;
const { password: _pw, ...userWithoutPassword } = user
// Format role and permissions
const role = user.employee?.position ? {
id: user.employee.position.id,
name: user.employee.position.titleAr || user.employee.position.title,
nameEn: user.employee.position.title,
permissions: user.employee.position.permissions || []
} : null;
const role = user.employee?.position
? {
id: user.employee.position.id,
name: user.employee.position.titleAr || user.employee.position.title,
nameEn: user.employee.position.title,
permissions: user.employee.position.permissions || []
}
: null
return {
user: {
...userWithoutPassword,
role
},
...tokens,
};
...tokens
}
}
async getUserById(userId: string) {
@@ -184,77 +171,57 @@ class AuthService {
include: {
employee: {
include: {
position: {
include: {
permissions: true,
},
},
department: true,
},
},
},
});
position: { include: { permissions: true } },
department: true
}
}
}
})
if (!user) {
throw new AppError(404, 'المستخدم غير موجود - User not found');
}
if (!user) throw new AppError(404, 'المستخدم غير موجود - User not found')
if (!user.isActive) throw new AppError(403, 'الحساب غير مفعل - Account is inactive')
if (!user.isActive) {
throw new AppError(403, 'الحساب غير مفعل - Account is inactive');
}
const { password: _pw, ...userWithoutPassword } = user
// Format user data
const { password: _, ...userWithoutPassword } = user;
const role = user.employee?.position
? {
id: user.employee.position.id,
name: user.employee.position.titleAr || user.employee.position.title,
nameEn: user.employee.position.title,
permissions: user.employee.position.permissions || []
}
: null
const role = user.employee?.position ? {
id: user.employee.position.id,
name: user.employee.position.titleAr || user.employee.position.title,
nameEn: user.employee.position.title,
permissions: user.employee.position.permissions || []
} : null;
return {
...userWithoutPassword,
role
};
return { ...userWithoutPassword, role }
}
async refreshToken(refreshToken: string) {
try {
const decoded = jwt.verify(refreshToken, config.jwt.secret) as {
id: string;
email: string;
};
// Verify refresh token matches stored token
const user = await prisma.user.findUnique({
where: { id: decoded.id },
});
const decoded = jwt.verify(refreshToken, config.jwt.secret) as { id: string; email: string }
const user = await prisma.user.findUnique({ where: { id: decoded.id } })
if (!user || user.refreshToken !== refreshToken || !user.isActive) {
throw new AppError(401, 'رمز غير صالح - Invalid token');
throw new AppError(401, 'رمز غير صالح - Invalid token')
}
// Generate new tokens
const tokens = this.generateTokens(user.id, user.email);
const tokens = this.generateTokens(user.id, user.email)
// Update refresh token
await prisma.user.update({
where: { id: user.id },
data: { refreshToken: tokens.refreshToken },
});
data: { refreshToken: tokens.refreshToken }
})
return tokens;
} catch (error) {
throw new AppError(401, 'رمز غير صالح - Invalid token');
return tokens
} catch {
throw new AppError(401, 'رمز غير صالح - Invalid token')
}
}
async logout(userId: string) {
await prisma.user.update({
where: { id: userId },
data: { refreshToken: null },
});
data: { refreshToken: null }
})
}
async getUserProfile(userId: string) {
@@ -268,46 +235,35 @@ class AuthService {
lastLogin: true,
employee: {
include: {
position: {
include: {
permissions: true,
},
},
department: true,
},
},
},
});
position: { include: { permissions: true } },
department: true
}
}
}
})
if (!user) {
throw new AppError(404, 'المستخدم غير موجود - User not found');
}
return user;
if (!user) throw new AppError(404, 'المستخدم غير موجود - User not found')
return user
}
private generateTokens(userId: string, email: string) {
const payload = { id: userId, email };
const secret = config.jwt.secret as Secret;
const payload = { id: userId, email }
const secret = config.jwt.secret as Secret
const accessToken = jwt.sign(
payload,
secret,
{ expiresIn: config.jwt.expiresIn } as SignOptions
);
const accessToken = jwt.sign(payload, secret, {
expiresIn: config.jwt.expiresIn
} as SignOptions)
const refreshToken = jwt.sign(
payload,
secret,
{ expiresIn: config.jwt.refreshExpiresIn } as SignOptions
);
const refreshToken = jwt.sign(payload, secret, {
expiresIn: config.jwt.refreshExpiresIn
} as SignOptions)
return {
accessToken,
refreshToken,
expiresIn: config.jwt.expiresIn,
};
expiresIn: config.jwt.expiresIn
}
}
}
export const authService = new AuthService();
export const authService = new AuthService()