@dax-crafta/auth 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/services/auth.services.js +161 -0
- package/src/utils/email.js +50 -0
package/package.json
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const { authenticator } = require('otplib');
|
|
3
|
+
const User = require('../models/user');
|
|
4
|
+
const EmailService = require('../utils/email');
|
|
5
|
+
|
|
6
|
+
class AuthService {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.emailService = new EmailService(config);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async register(userData) {
|
|
13
|
+
const user = new User({
|
|
14
|
+
...userData,
|
|
15
|
+
customFields: this.extractCustomFields(userData)
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const verificationToken = this.generateToken();
|
|
19
|
+
user.verificationToken = verificationToken;
|
|
20
|
+
await user.save();
|
|
21
|
+
|
|
22
|
+
if (this.config.emailVerification) {
|
|
23
|
+
await this.emailService.sendVerificationEmail(user, verificationToken);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return user;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async login(email, password, deviceInfo) {
|
|
30
|
+
const user = await User.findOne({ email });
|
|
31
|
+
|
|
32
|
+
if (!user || user.isLocked()) {
|
|
33
|
+
throw new Error('Invalid credentials or account locked');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isValid = await user.comparePassword(password);
|
|
37
|
+
if (!isValid) {
|
|
38
|
+
await this.handleFailedLogin(user);
|
|
39
|
+
throw new Error('Invalid credentials');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (user.twoFactorEnabled) {
|
|
43
|
+
const code = authenticator.generate(user.twoFactorSecret);
|
|
44
|
+
await this.emailService.send2FACode(user, code);
|
|
45
|
+
return { requires2FA: true };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.config.loginAlerts) {
|
|
49
|
+
await this.emailService.sendLoginAlert(user, deviceInfo);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { accessToken, refreshToken } = this.generateTokens(user);
|
|
53
|
+
return { accessToken, refreshToken, user };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async verify2FA(userId, code) {
|
|
57
|
+
const user = await User.findById(userId);
|
|
58
|
+
const isValid = authenticator.verify({
|
|
59
|
+
token: code,
|
|
60
|
+
secret: user.twoFactorSecret
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!isValid) {
|
|
64
|
+
throw new Error('Invalid 2FA code');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { accessToken, refreshToken } = this.generateTokens(user);
|
|
68
|
+
return { accessToken, refreshToken, user };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async refreshToken(token) {
|
|
72
|
+
const user = await User.findOne({ 'refreshTokens.token': token });
|
|
73
|
+
if (!user) {
|
|
74
|
+
throw new Error('Invalid refresh token');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { accessToken, refreshToken } = this.generateTokens(user);
|
|
78
|
+
return { accessToken, refreshToken, user };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async forgotPassword(email) {
|
|
82
|
+
const user = await User.findOne({ email });
|
|
83
|
+
if (!user) {
|
|
84
|
+
throw new Error('User not found');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const token = this.generateToken();
|
|
88
|
+
user.passwordResetToken = token;
|
|
89
|
+
user.passwordResetExpires = Date.now() + 3600000; // 1 hour
|
|
90
|
+
await user.save();
|
|
91
|
+
|
|
92
|
+
await this.emailService.sendPasswordResetEmail(user, token);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async resetPassword(token, newPassword) {
|
|
96
|
+
const user = await User.findOne({
|
|
97
|
+
passwordResetToken: token,
|
|
98
|
+
passwordResetExpires: { $gt: Date.now() }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!user) {
|
|
102
|
+
throw new Error('Invalid or expired reset token');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
user.password = newPassword;
|
|
106
|
+
user.passwordResetToken = undefined;
|
|
107
|
+
user.passwordResetExpires = undefined;
|
|
108
|
+
await user.save();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async updateProfile(userId, updates) {
|
|
112
|
+
const user = await User.findById(userId);
|
|
113
|
+
Object.assign(user, updates);
|
|
114
|
+
await user.save();
|
|
115
|
+
return user;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private generateTokens(user) {
|
|
119
|
+
const accessToken = jwt.sign(
|
|
120
|
+
{ id: user._id, email: user.email, role: user.role },
|
|
121
|
+
this.config.env.JWT_SECRET,
|
|
122
|
+
{ expiresIn: '1h' }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const refreshToken = this.generateToken();
|
|
126
|
+
user.refreshTokens.push({
|
|
127
|
+
token: refreshToken,
|
|
128
|
+
expires: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
129
|
+
});
|
|
130
|
+
user.save();
|
|
131
|
+
|
|
132
|
+
return { accessToken, refreshToken };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private generateToken() {
|
|
136
|
+
return jwt.sign(
|
|
137
|
+
{ random: Math.random() },
|
|
138
|
+
this.config.env.JWT_SECRET
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async handleFailedLogin(user) {
|
|
143
|
+
user.loginAttempts += 1;
|
|
144
|
+
if (user.loginAttempts >= this.config.maxLoginAttempts) {
|
|
145
|
+
user.lockUntil = Date.now() + 3600000; // 1 hour
|
|
146
|
+
}
|
|
147
|
+
await user.save();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private extractCustomFields(userData) {
|
|
151
|
+
const standardFields = ['email', 'password', 'role'];
|
|
152
|
+
return Object.keys(userData)
|
|
153
|
+
.filter(key => !standardFields.includes(key))
|
|
154
|
+
.reduce((obj, key) => {
|
|
155
|
+
obj[key] = userData[key];
|
|
156
|
+
return obj;
|
|
157
|
+
}, {});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = AuthService;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const nodemailer = require('nodemailer');
|
|
2
|
+
|
|
3
|
+
class EmailService {
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.transporter = nodemailer.createTransport(config.smtp);
|
|
6
|
+
this.from = config.smtp.from;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async sendVerificationEmail(user, token) {
|
|
10
|
+
const verificationUrl = `${this.config.baseUrl}/verify-email?token=${token}`;
|
|
11
|
+
|
|
12
|
+
await this.transporter.sendMail({
|
|
13
|
+
from: this.from,
|
|
14
|
+
to: user.email,
|
|
15
|
+
subject: 'Verify Your Email',
|
|
16
|
+
html: `Click here to verify your email: <a href="${verificationUrl}">${verificationUrl}</a>`
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async sendPasswordResetEmail(user, token) {
|
|
21
|
+
const resetUrl = `${this.config.baseUrl}/reset-password?token=${token}`;
|
|
22
|
+
|
|
23
|
+
await this.transporter.sendMail({
|
|
24
|
+
from: this.from,
|
|
25
|
+
to: user.email,
|
|
26
|
+
subject: 'Reset Your Password',
|
|
27
|
+
html: `Click here to reset your password: <a href="${resetUrl}">${resetUrl}</a>`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async sendLoginAlert(user, deviceInfo) {
|
|
32
|
+
await this.transporter.sendMail({
|
|
33
|
+
from: this.from,
|
|
34
|
+
to: user.email,
|
|
35
|
+
subject: 'New Login Detected',
|
|
36
|
+
html: `New login detected from ${deviceInfo.browser} on ${deviceInfo.os}`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async send2FACode(user, code) {
|
|
41
|
+
await this.transporter.sendMail({
|
|
42
|
+
from: this.from,
|
|
43
|
+
to: user.email,
|
|
44
|
+
subject: '2FA Code',
|
|
45
|
+
html: `Your 2FA code is: ${code}`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = EmailService;
|