UNPKG

@wikiccu/nest-auth

Version:

A comprehensive authentication package for NestJS applications with Prisma and PostgreSQL

484 lines 17.8 kB
"use strict"; 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = void 0; const common_1 = require("@nestjs/common"); const jwt_1 = require("@nestjs/jwt"); const prisma_service_1 = require("./prisma.service"); const email_service_1 = require("./email.service"); const password_service_1 = require("./password.service"); const token_service_1 = require("./token.service"); const config_1 = require("@nestjs/config"); let AuthService = class AuthService { prisma; jwtService; emailService; passwordService; tokenService; configService; constructor(prisma, jwtService, emailService, passwordService, tokenService, configService) { this.prisma = prisma; this.jwtService = jwtService; this.emailService = emailService; this.passwordService = passwordService; this.tokenService = tokenService; this.configService = configService; } async register(registerDto) { const { email, password, username, firstName, lastName } = registerDto; const existingUser = await this.prisma.user.findFirst({ where: { OR: [ { email }, ...(username ? [{ username }] : []), ], }, }); if (existingUser) { throw new common_1.ConflictException('User with this email or username already exists'); } const hashedPassword = await this.passwordService.hashPassword(password); const user = await this.prisma.user.create({ data: { email, username, password: hashedPassword, firstName, lastName, }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); await this.sendEmailVerification(user.id, user.email); const authUser = this.mapToAuthUser(user); return { user: authUser, message: 'User registered successfully. Please check your email to verify your account.', }; } async login(loginDto, sessionInfo) { const { email, password, rememberMe } = loginDto; const user = await this.prisma.user.findUnique({ where: { email }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); if (!user || !user.isActive) { throw new common_1.UnauthorizedException('Invalid credentials'); } const isPasswordValid = await this.passwordService.verifyPassword(password, user.password); if (!isPasswordValid) { throw new common_1.UnauthorizedException('Invalid credentials'); } await this.prisma.user.update({ where: { id: user.id }, data: { lastLoginAt: new Date() }, }); const authUser = this.mapToAuthUser(user); const accessToken = await this.generateAccessToken(authUser); const refreshToken = await this.tokenService.generateRefreshToken(user.id, rememberMe); if (sessionInfo) { await this.tokenService.createSession(user.id, accessToken, sessionInfo); } return { user: authUser, accessToken, refreshToken, expiresIn: this.getTokenExpirationTime(), }; } async refreshToken(refreshTokenDto) { const { refreshToken } = refreshTokenDto; const payload = await this.tokenService.verifyRefreshToken(refreshToken); if (!payload) { throw new common_1.UnauthorizedException('Invalid refresh token'); } const user = await this.prisma.user.findUnique({ where: { id: payload.userId }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); if (!user || !user.isActive) { throw new common_1.UnauthorizedException('User not found or inactive'); } const authUser = this.mapToAuthUser(user); const newAccessToken = await this.generateAccessToken(authUser); const newRefreshToken = await this.tokenService.rotateRefreshToken(refreshToken, user.id); return { accessToken: newAccessToken, refreshToken: newRefreshToken, expiresIn: this.getTokenExpirationTime(), }; } async logout(logoutDto) { const { refreshToken } = logoutDto; await this.tokenService.revokeRefreshToken(refreshToken); return { message: 'Logged out successfully' }; } async forgotPassword(forgotPasswordDto) { const { email } = forgotPasswordDto; const user = await this.prisma.user.findUnique({ where: { email }, }); if (user) { await this.sendPasswordResetEmail(user.id, user.email); } return { message: 'If an account with that email exists, a password reset link has been sent.' }; } async resetPassword(resetPasswordDto) { const { token, newPassword } = resetPasswordDto; const payload = await this.tokenService.verifyPasswordResetToken(token); if (!payload) { throw new common_1.BadRequestException('Invalid or expired reset token'); } const hashedPassword = await this.passwordService.hashPassword(newPassword); await this.prisma.$transaction([ this.prisma.user.update({ where: { id: payload.userId }, data: { password: hashedPassword }, }), this.prisma.passwordResetToken.update({ where: { token }, data: { isUsed: true }, }), ]); return { message: 'Password reset successfully' }; } async changePassword(userId, changePasswordDto) { const { currentPassword, newPassword } = changePasswordDto; const user = await this.prisma.user.findUnique({ where: { id: userId }, }); if (!user) { throw new common_1.UnauthorizedException('User not found'); } const isCurrentPasswordValid = await this.passwordService.verifyPassword(currentPassword, user.password); if (!isCurrentPasswordValid) { throw new common_1.BadRequestException('Current password is incorrect'); } const hashedPassword = await this.passwordService.hashPassword(newPassword); await this.prisma.user.update({ where: { id: userId }, data: { password: hashedPassword }, }); return { message: 'Password changed successfully' }; } async verifyEmail(verifyEmailDto) { const { token } = verifyEmailDto; const payload = await this.tokenService.verifyEmailVerificationToken(token); if (!payload) { throw new common_1.BadRequestException('Invalid or expired verification token'); } await this.prisma.$transaction([ this.prisma.user.update({ where: { id: payload.userId }, data: { isEmailVerified: true, emailVerifiedAt: new Date(), }, }), this.prisma.emailVerificationToken.update({ where: { token }, data: { isUsed: true }, }), ]); return { message: 'Email verified successfully' }; } async resendVerification(resendVerificationDto) { const { email } = resendVerificationDto; const user = await this.prisma.user.findUnique({ where: { email }, }); if (!user) { throw new common_1.BadRequestException('User not found'); } if (user.isEmailVerified) { throw new common_1.BadRequestException('Email is already verified'); } await this.sendEmailVerification(user.id, user.email); return { message: 'Verification email sent successfully' }; } async getProfile(userId) { const user = await this.prisma.user.findUnique({ where: { id: userId }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); if (!user) { throw new common_1.UnauthorizedException('User not found'); } return this.mapToAuthUser(user); } async updateProfile(userId, updateUserDto) { const { email, username, firstName, lastName } = updateUserDto; if (email || username) { const existingUser = await this.prisma.user.findFirst({ where: { OR: [ ...(email ? [{ email }] : []), ...(username ? [{ username }] : []), ], NOT: { id: userId }, }, }); if (existingUser) { throw new common_1.ConflictException('Email or username already exists'); } } const user = await this.prisma.user.update({ where: { id: userId }, data: { email, username, firstName, lastName, }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); return this.mapToAuthUser(user); } async createUser(createUserDto) { const { email, password, username, firstName, lastName, roles } = createUserDto; const existingUser = await this.prisma.user.findFirst({ where: { OR: [ { email }, ...(username ? [{ username }] : []), ], }, }); if (existingUser) { throw new common_1.ConflictException('User with this email or username already exists'); } const hashedPassword = await this.passwordService.hashPassword(password); const user = await this.prisma.user.create({ data: { email, username, password: hashedPassword, firstName, lastName, isEmailVerified: true, emailVerifiedAt: new Date(), ...(roles && { roles: { create: roles.map(roleName => ({ role: { connect: { name: roleName }, }, })), }, }), }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); return this.mapToAuthUser(user); } async updateUser(userId, updateUserDto) { const { email, username, firstName, lastName, isActive, roles } = updateUserDto; if (email || username) { const existingUser = await this.prisma.user.findFirst({ where: { OR: [ ...(email ? [{ email }] : []), ...(username ? [{ username }] : []), ], NOT: { id: userId }, }, }); if (existingUser) { throw new common_1.ConflictException('Email or username already exists'); } } const user = await this.prisma.user.update({ where: { id: userId }, data: { email, username, firstName, lastName, isActive, ...(roles && { roles: { deleteMany: {}, create: roles.map(roleName => ({ role: { connect: { name: roleName }, }, })), }, }), }, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, }); return this.mapToAuthUser(user); } async deleteUser(userId) { await this.prisma.user.delete({ where: { id: userId }, }); return { message: 'User deleted successfully' }; } async getUsers(page = 1, limit = 10) { const skip = (page - 1) * limit; const [users, total] = await Promise.all([ this.prisma.user.findMany({ skip, take: limit, include: { roles: { include: { role: { include: { permissions: true, }, }, }, }, }, orderBy: { createdAt: 'desc' }, }), this.prisma.user.count(), ]); return { users: users.map(user => this.mapToAuthUser(user)), total, page, limit, }; } async generateAccessToken(authUser) { const payload = { sub: authUser.id, email: authUser.email, username: authUser.username, roles: authUser.roles, permissions: authUser.permissions, }; return this.jwtService.signAsync(payload); } getTokenExpirationTime() { const expiresIn = this.configService.get('JWT_EXPIRES_IN', '15m'); const match = expiresIn.match(/^(\d+)([mhd])$/); if (!match) return 15 * 60; const [, value, unit] = match; const numValue = parseInt(value, 10); switch (unit) { case 'm': return numValue * 60; case 'h': return numValue * 60 * 60; case 'd': return numValue * 60 * 60 * 24; default: return 15 * 60; } } async sendEmailVerification(userId, email) { const token = await this.tokenService.generateEmailVerificationToken(userId); await this.emailService.sendEmailVerification(email, token); } async sendPasswordResetEmail(userId, email) { const token = await this.tokenService.generatePasswordResetToken(userId); await this.emailService.sendPasswordReset(email, token); } mapToAuthUser(user) { return { id: user.id, email: user.email, username: user.username, firstName: user.firstName, lastName: user.lastName, isEmailVerified: user.isEmailVerified, isActive: user.isActive, roles: user.roles.map((ur) => ur.role.name), permissions: user.roles.flatMap((ur) => ur.role.permissions.map((p) => p.name)), }; } }; exports.AuthService = AuthService; exports.AuthService = AuthService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [prisma_service_1.PrismaService, jwt_1.JwtService, email_service_1.EmailService, password_service_1.PasswordService, token_service_1.TokenService, config_1.ConfigService]) ], AuthService); //# sourceMappingURL=auth.service.js.map