UNPKG

@dax-crafta/auth

Version:

A powerful, flexible, and secure authentication plugin for the Crafta framework. Supports JWT, social login, 2FA, RBAC, audit logging, and enterprise-grade security features.

161 lines (131 loc) 4.39 kB
const jwt = require('jsonwebtoken'); const { authenticator } = require('otplib'); const User = require('../models/user'); const EmailService = require('../utils/email'); class AuthService { constructor(config) { this.config = config; this.emailService = new EmailService(config); } async register(userData) { const user = new User({ ...userData, customFields: this.extractCustomFields(userData) }); const verificationToken = this.generateToken(); user.verificationToken = verificationToken; await user.save(); if (this.config.emailVerification) { await this.emailService.sendVerificationEmail(user, verificationToken); } return user; } async login(email, password, deviceInfo) { const user = await User.findOne({ email }); if (!user || user.isLocked()) { throw new Error('Invalid credentials or account locked'); } const isValid = await user.comparePassword(password); if (!isValid) { await this.handleFailedLogin(user); throw new Error('Invalid credentials'); } if (user.twoFactorEnabled) { const code = authenticator.generate(user.twoFactorSecret); await this.emailService.send2FACode(user, code); return { requires2FA: true }; } if (this.config.loginAlerts) { await this.emailService.sendLoginAlert(user, deviceInfo); } const { accessToken, refreshToken } = this.generateTokens(user); return { accessToken, refreshToken, user }; } async verify2FA(userId, code) { const user = await User.findById(userId); const isValid = authenticator.verify({ token: code, secret: user.twoFactorSecret }); if (!isValid) { throw new Error('Invalid 2FA code'); } const { accessToken, refreshToken } = this.generateTokens(user); return { accessToken, refreshToken, user }; } async refreshToken(token) { const user = await User.findOne({ 'refreshTokens.token': token }); if (!user) { throw new Error('Invalid refresh token'); } const { accessToken, refreshToken } = this.generateTokens(user); return { accessToken, refreshToken, user }; } async forgotPassword(email) { const user = await User.findOne({ email }); if (!user) { throw new Error('User not found'); } const token = this.generateToken(); user.passwordResetToken = token; user.passwordResetExpires = Date.now() + 3600000; // 1 hour await user.save(); await this.emailService.sendPasswordResetEmail(user, token); } async resetPassword(token, newPassword) { const user = await User.findOne({ passwordResetToken: token, passwordResetExpires: { $gt: Date.now() } }); if (!user) { throw new Error('Invalid or expired reset token'); } user.password = newPassword; user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save(); } async updateProfile(userId, updates) { const user = await User.findById(userId); Object.assign(user, updates); await user.save(); return user; } private generateTokens(user) { const accessToken = jwt.sign( { id: user._id, email: user.email, role: user.role }, this.config.env.JWT_SECRET, { expiresIn: '1h' } ); const refreshToken = this.generateToken(); user.refreshTokens.push({ token: refreshToken, expires: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days }); user.save(); return { accessToken, refreshToken }; } private generateToken() { return jwt.sign( { random: Math.random() }, this.config.env.JWT_SECRET ); } private async handleFailedLogin(user) { user.loginAttempts += 1; if (user.loginAttempts >= this.config.maxLoginAttempts) { user.lockUntil = Date.now() + 3600000; // 1 hour } await user.save(); } private extractCustomFields(userData) { const standardFields = ['email', 'password', 'role']; return Object.keys(userData) .filter(key => !standardFields.includes(key)) .reduce((obj, key) => { obj[key] = userData[key]; return obj; }, {}); } } module.exports = AuthService;