@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
JavaScript
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;