127 lines
5.5 KiB
JavaScript
127 lines
5.5 KiB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
};
|
|
import { createHash, randomBytes } from "node:crypto";
|
|
import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
import { JwtService } from "@nestjs/jwt";
|
|
import { hash, verify } from "@node-rs/argon2";
|
|
import { env } from "../config/env.js";
|
|
import { AuditService } from "../common/common.module.js";
|
|
import { PrismaService } from "../prisma/prisma.service.js";
|
|
export const hashPassword = (password) => hash(password);
|
|
const sha256 = (v) => createHash("sha256").update(v).digest("hex");
|
|
export const toUserDto = (u) => ({
|
|
id: u.id,
|
|
phone: u.phone,
|
|
name: u.name,
|
|
role: u.role,
|
|
isActive: u.isActive,
|
|
lastLoginAt: u.lastLoginAt?.toISOString() ?? null,
|
|
createdAt: u.createdAt.toISOString(),
|
|
});
|
|
const INVALID_LOGIN = "شماره موبایل یا رمز عبور اشتباه است";
|
|
const DUMMY_HASH = hashPassword(randomBytes(16).toString("hex"));
|
|
let AuthService = class AuthService {
|
|
prisma;
|
|
jwt;
|
|
audit;
|
|
constructor(prisma, jwt, audit) {
|
|
this.prisma = prisma;
|
|
this.jwt = jwt;
|
|
this.audit = audit;
|
|
}
|
|
async login(input, userAgent) {
|
|
const user = await this.prisma.adminUser.findUnique({ where: { phone: input.phone } });
|
|
const ok = await verify(user?.passwordHash ?? (await DUMMY_HASH), input.password).catch(() => false);
|
|
if (!user || !ok || !user.isActive)
|
|
throw new UnauthorizedException(INVALID_LOGIN);
|
|
const updated = await this.prisma.adminUser.update({
|
|
where: { id: user.id },
|
|
data: { lastLoginAt: new Date() },
|
|
});
|
|
this.audit.log(user.id, "login", "user", user.id, user.phone);
|
|
return this.issue(updated, userAgent);
|
|
}
|
|
async refresh(token, userAgent) {
|
|
if (!token)
|
|
throw new UnauthorizedException("نشست شما منقضی شده است");
|
|
const stored = await this.prisma.refreshToken.findUnique({
|
|
where: { tokenHash: sha256(token) },
|
|
include: { user: true },
|
|
});
|
|
if (!stored)
|
|
throw new UnauthorizedException("نشست شما منقضی شده است");
|
|
if (stored.revokedAt) {
|
|
await this.revokeAll(stored.userId);
|
|
throw new UnauthorizedException("نشست شما منقضی شده است");
|
|
}
|
|
if (stored.expiresAt < new Date() || !stored.user.isActive) {
|
|
throw new UnauthorizedException("نشست شما منقضی شده است");
|
|
}
|
|
await this.prisma.refreshToken.update({
|
|
where: { id: stored.id },
|
|
data: { revokedAt: new Date() },
|
|
});
|
|
return this.issue(stored.user, userAgent);
|
|
}
|
|
async logout(token) {
|
|
if (!token)
|
|
return;
|
|
await this.prisma.refreshToken.updateMany({
|
|
where: { tokenHash: sha256(token), revokedAt: null },
|
|
data: { revokedAt: new Date() },
|
|
});
|
|
}
|
|
async changePassword(userId, input) {
|
|
const user = await this.prisma.adminUser.findUniqueOrThrow({ where: { id: userId } });
|
|
if (!(await verify(user.passwordHash, input.currentPassword).catch(() => false))) {
|
|
throw new BadRequestException("رمز عبور فعلی اشتباه است");
|
|
}
|
|
await this.prisma.adminUser.update({
|
|
where: { id: userId },
|
|
data: { passwordHash: await hashPassword(input.newPassword) },
|
|
});
|
|
this.audit.log(userId, "change-password", "user", userId, user.phone);
|
|
}
|
|
async me(userId) {
|
|
return toUserDto(await this.prisma.adminUser.findUniqueOrThrow({ where: { id: userId } }));
|
|
}
|
|
revokeAll(userId) {
|
|
return this.prisma.refreshToken.updateMany({
|
|
where: { userId, revokedAt: null },
|
|
data: { revokedAt: new Date() },
|
|
});
|
|
}
|
|
async issue(user, userAgent) {
|
|
const payload = { sub: user.id, role: user.role };
|
|
const accessToken = await this.jwt.signAsync(payload);
|
|
const refreshToken = randomBytes(48).toString("base64url");
|
|
const refreshExpiresAt = new Date(Date.now() + env.REFRESH_TTL_DAYS * 86_400_000);
|
|
await this.prisma.refreshToken.create({
|
|
data: {
|
|
userId: user.id,
|
|
tokenHash: sha256(refreshToken),
|
|
expiresAt: refreshExpiresAt,
|
|
userAgent: userAgent.slice(0, 300),
|
|
},
|
|
});
|
|
void this.prisma.refreshToken
|
|
.deleteMany({ where: { expiresAt: { lt: new Date(Date.now() - 86_400_000) } } })
|
|
.catch(() => undefined);
|
|
return { accessToken, refreshToken, refreshExpiresAt, user: toUserDto(user) };
|
|
}
|
|
};
|
|
AuthService = __decorate([
|
|
Injectable(),
|
|
__metadata("design:paramtypes", [PrismaService,
|
|
JwtService,
|
|
AuditService])
|
|
], AuthService);
|
|
export { AuthService };
|
|
//# sourceMappingURL=auth.service.js.map
|