Spaces:
Runtime error
Runtime error
Download apps/server/src/modules/auth/auth.service.js from moustafa7/flwo: direct link, hf CLI and curl.
- Browser
- Download file 5.14 kB
-
https://huggingface.co/spaces/moustafa7/flwo/resolve/main/apps/server/src/modules/auth/auth.service.js
- Command line
-
hf download hf://spaces/moustafa7/flwo/apps/server/src/modules/auth/auth.service.js
-
curl -L -o auth.service.js https://huggingface.co/spaces/moustafa7/flwo/resolve/main/apps/server/src/modules/auth/auth.service.js
5.14 kB
| import bcrypt from 'bcryptjs' | |
| import { createHmac } from 'crypto' | |
| import { prisma } from '../../config/database.js' | |
| import { signAccessToken, signRefreshToken, verifyRefreshToken, blacklistToken } from '../../lib/jwt.js' | |
| import { AppError } from '../../lib/AppError.js' | |
| import { sendEmail, WELCOME } from '../../lib/email.js' | |
| import { config } from '../../config/index.js' | |
| import { SAFE_USER_SELECT } from '../../lib/constants.js' | |
| const BCRYPT_ROUNDS = 12 | |
| const TIMING_DUMMY_HASH = bcrypt.hashSync('dummy-timing-attack-protection', BCRYPT_ROUNDS) | |
| export function hashToken(token) { | |
| return createHmac('sha256', config.TOKEN_HASH_SECRET) | |
| .update(token) | |
| .digest('hex') | |
| } | |
| function parseExpires(expiresStr) { | |
| const match = expiresStr.match(/^(\d+)([dhms])$/) | |
| if (!match) return 7 * 24 * 60 * 60 * 1000 | |
| const num = parseInt(match[1]) | |
| const unit = match[2] | |
| const mul = { d: 86400000, h: 3600000, m: 60000, s: 1000 } | |
| return num * (mul[unit] || 86400000) | |
| } | |
| async function createTokenPair(userId, role) { | |
| const accessToken = signAccessToken(userId, role) | |
| const refreshToken = signRefreshToken(userId) | |
| const hashedRefresh = hashToken(refreshToken) | |
| const expiresAt = new Date(Date.now() + parseExpires(config.JWT_REFRESH_EXPIRES)) | |
| await prisma.refreshToken.create({ | |
| data: { token: hashedRefresh, userId, expiresAt } | |
| }) | |
| return { accessToken, refreshToken } | |
| } | |
| export async function registerUser(data, avatarUrl = null) { | |
| const existing = await prisma.user.findFirst({ | |
| where: { OR: [{ email: data.email }, { phone: data.phone }] }, | |
| select: { id: true } | |
| }) | |
| if (existing) { | |
| throw new AppError('البيانات المدخلة مستخدمة بالفعل', 409, 'DUPLICATE_ENTRY') | |
| } | |
| const passwordHash = await bcrypt.hash(data.password, BCRYPT_ROUNDS) | |
| const user = await prisma.user.create({ | |
| data: { | |
| firstName: data.firstName, | |
| lastName: data.lastName, | |
| phone: data.phone, | |
| email: data.email, | |
| passwordHash, | |
| avatarUrl, | |
| role: 'CLIENT' | |
| }, | |
| select: SAFE_USER_SELECT, | |
| }) | |
| const { accessToken, refreshToken } = await createTokenPair(user.id, user.role) | |
| sendEmail(data.email, WELCOME(user)).catch(() => {}) | |
| return { user, accessToken, refreshToken } | |
| } | |
| export async function loginUser(email, password) { | |
| const user = await prisma.user.findUnique({ | |
| where: { email }, | |
| select: { ...SAFE_USER_SELECT, passwordHash: true } | |
| }) | |
| const isValid = await bcrypt.compare(password, user?.passwordHash || TIMING_DUMMY_HASH) | |
| if (!user || !isValid || !user.isActive) { | |
| throw new AppError('بيانات تسجيل الدخول غير صحيحة', 401, 'INVALID_CREDENTIALS') | |
| } | |
| const { accessToken, refreshToken } = await createTokenPair(user.id, user.role) | |
| const { passwordHash, ...safeUser } = user | |
| return { user: safeUser, accessToken, refreshToken } | |
| } | |
| export async function refreshUserToken(oldRefreshToken, expectClient = true) { | |
| if (!oldRefreshToken) throw new AppError('لم يتم توفير refresh token', 401, 'NO_REFRESH_TOKEN') | |
| let decoded | |
| try { decoded = verifyRefreshToken(oldRefreshToken) } | |
| catch { throw new AppError('Refresh token منتهي أو غير صحيح', 401, 'INVALID_REFRESH_TOKEN') } | |
| const hashedOld = hashToken(oldRefreshToken) | |
| const stored = await prisma.refreshToken.findUnique({ where: { token: hashedOld } }) | |
| if (!stored || stored.expiresAt < new Date()) { | |
| if (stored) await prisma.refreshToken.deleteMany({ where: { userId: decoded.sub } }) | |
| throw new AppError('Refresh token غير صحيح', 401, 'REFRESH_TOKEN_REUSE') | |
| } | |
| await prisma.refreshToken.delete({ where: { token: hashedOld } }) | |
| const user = await prisma.user.findUnique({ | |
| where: { id: decoded.sub }, | |
| select: SAFE_USER_SELECT, | |
| }) | |
| if (!user || !user.isActive) { | |
| throw new AppError('بيانات تسجيل الدخول غير صحيحة', 401, 'INVALID_CREDENTIALS') | |
| } | |
| if (expectClient && user.role !== 'CLIENT') { | |
| throw new AppError('نوع الحساب غير متطابق', 401, 'ROLE_MISMATCH') | |
| } | |
| const { accessToken, refreshToken } = await createTokenPair(user.id, user.role) | |
| const isAdmin = user.role !== 'CLIENT' | |
| return { user, accessToken, refreshToken, isAdmin } | |
| } | |
| export async function changeUserPassword(userId, currentPassword, newPassword) { | |
| const user = await prisma.user.findUnique({ | |
| where: { id: userId }, | |
| select: { passwordHash: true } | |
| }) | |
| if (!user) throw new AppError('المستخدم غير موجود', 404, 'USER_NOT_FOUND') | |
| const isValid = await bcrypt.compare(currentPassword, user.passwordHash) | |
| if (!isValid) throw new AppError('كلمة المرور الحالية غير صحيحة', 401, 'INVALID_PASSWORD') | |
| const passwordHash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS) | |
| await prisma.$transaction([ | |
| prisma.user.update({ where: { id: userId }, data: { passwordHash } }), | |
| prisma.refreshToken.deleteMany({ where: { userId } }), | |
| ]) | |
| } | |
| export async function logoutUser(userId, accessToken) { | |
| if (accessToken) await blacklistToken(accessToken) | |
| await prisma.refreshToken.deleteMany({ where: { userId } }) | |
| } | |