@wikiccu/nest-auth
Version:
A comprehensive authentication package for NestJS applications with Prisma and PostgreSQL
484 lines • 17.8 kB
JavaScript
"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