@dax-crafta/auth 1.2.4 → 1.2.5
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/index.js +165 -145
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,204 +1,224 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
1
|
+
const passport = require('passport');
|
|
2
|
+
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
|
3
|
+
const FacebookStrategy = require('passport-facebook').Strategy;
|
|
4
|
+
const GitHubStrategy = require('passport-github2').Strategy;
|
|
5
|
+
const AuthService = require('./services/auth.service');
|
|
6
|
+
const createAuthMiddleware = require('./middlewares/auth.middleware');
|
|
5
7
|
|
|
6
|
-
// Default configuration
|
|
7
8
|
const defaultConfig = {
|
|
8
9
|
strategy: 'jwt',
|
|
9
10
|
fields: ['email', 'password'],
|
|
10
11
|
routes: {
|
|
11
12
|
register: '/register',
|
|
12
13
|
login: '/login',
|
|
13
|
-
verify: '/verify'
|
|
14
|
+
verify: '/verify',
|
|
15
|
+
forgotPassword: '/forgot-password',
|
|
16
|
+
resetPassword: '/reset-password',
|
|
17
|
+
refreshToken: '/refresh-token',
|
|
18
|
+
profile: '/profile',
|
|
19
|
+
twoFactor: '/2fa'
|
|
14
20
|
},
|
|
15
21
|
mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/forge-auth',
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
maxLoginAttempts: 5,
|
|
23
|
+
emailVerification: true,
|
|
24
|
+
loginAlerts: true,
|
|
25
|
+
smtp: null,
|
|
26
|
+
social: {
|
|
27
|
+
google: null,
|
|
28
|
+
facebook: null,
|
|
29
|
+
github: null
|
|
30
|
+
}
|
|
18
31
|
};
|
|
19
32
|
|
|
20
|
-
// Create dynamic Mongoose schema based on fields
|
|
21
|
-
function createUserSchema(fields) {
|
|
22
|
-
const schemaFields = fields.reduce((acc, field) => {
|
|
23
|
-
acc[field] = {
|
|
24
|
-
type: String,
|
|
25
|
-
required: true,
|
|
26
|
-
...(field === 'email' && {
|
|
27
|
-
validate: {
|
|
28
|
-
validator: validator.isEmail,
|
|
29
|
-
message: 'Invalid email format'
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
};
|
|
33
|
-
return acc;
|
|
34
|
-
}, {});
|
|
35
|
-
|
|
36
|
-
return new mongoose.Schema(schemaFields, { timestamps: true });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
33
|
function auth(config = {}) {
|
|
40
34
|
const finalConfig = { ...defaultConfig, ...config };
|
|
41
35
|
|
|
42
|
-
// Validate JWT secret
|
|
43
36
|
if (!finalConfig.env?.JWT_SECRET) {
|
|
44
37
|
throw new Error('JWT_SECRET is required in environment configuration');
|
|
45
38
|
}
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (!finalConfig.db) {
|
|
50
|
-
mongoose.connect(finalConfig.mongoUrl, {
|
|
51
|
-
useNewUrlParser: true,
|
|
52
|
-
useUnifiedTopology: true
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const userSchema = createUserSchema(finalConfig.fields);
|
|
56
|
-
User = mongoose.model('User', userSchema);
|
|
40
|
+
if (finalConfig.emailVerification && !finalConfig.smtp) {
|
|
41
|
+
throw new Error('SMTP configuration is required for email verification');
|
|
57
42
|
}
|
|
58
43
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
const authService = new AuthService(finalConfig);
|
|
45
|
+
const { rateLimiter, verifyToken, checkRole } = createAuthMiddleware(finalConfig);
|
|
46
|
+
|
|
47
|
+
// Configure social login strategies
|
|
48
|
+
if (finalConfig.social.google) {
|
|
49
|
+
passport.use(new GoogleStrategy(finalConfig.social.google,
|
|
50
|
+
async (accessToken, refreshToken, profile, done) => {
|
|
51
|
+
try {
|
|
52
|
+
// Handle Google login
|
|
53
|
+
const user = await authService.handleSocialLogin('google', profile);
|
|
54
|
+
done(null, user);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
done(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
));
|
|
60
|
+
}
|
|
69
61
|
|
|
70
62
|
return function(app) {
|
|
63
|
+
app.use(passport.initialize());
|
|
64
|
+
app.use(rateLimiter);
|
|
65
|
+
|
|
71
66
|
// Register route
|
|
72
67
|
app.post(finalConfig.routes.register, async (req, res) => {
|
|
73
68
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Hash password
|
|
87
|
-
if (userData.password) {
|
|
88
|
-
userData.password = await bcrypt.hash(userData.password, 10);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Save user
|
|
92
|
-
const savedUser = await dbOperations.saveUser(userData);
|
|
69
|
+
const user = await authService.register(req.body);
|
|
70
|
+
res.status(201).json({
|
|
71
|
+
success: true,
|
|
72
|
+
message: 'Registration successful. Please verify your email.'
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
res.status(400).json({
|
|
76
|
+
success: false,
|
|
77
|
+
error: error.message
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
93
81
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
// Login route with device detection
|
|
83
|
+
app.post(finalConfig.routes.login, async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const deviceInfo = {
|
|
86
|
+
browser: req.headers['user-agent'],
|
|
87
|
+
ip: req.ip
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const result = await authService.login(
|
|
91
|
+
req.body.email,
|
|
92
|
+
req.body.password,
|
|
93
|
+
deviceInfo
|
|
99
94
|
);
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
if (finalConfig.sendVerificationEmail) {
|
|
103
|
-
try {
|
|
104
|
-
await finalConfig.sendVerificationEmail(savedUser);
|
|
105
|
-
} catch (emailError) {
|
|
106
|
-
console.error('Email verification failed:', emailError);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
res.status(201).json({
|
|
96
|
+
res.json({
|
|
111
97
|
success: true,
|
|
112
|
-
|
|
113
|
-
user: { ...savedUser.toJSON(), password: undefined }
|
|
98
|
+
...result
|
|
114
99
|
});
|
|
115
100
|
} catch (error) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
error: 'Email already exists'
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
res.status(400).json({
|
|
123
|
-
success: false,
|
|
124
|
-
error: error.message
|
|
101
|
+
res.status(401).json({
|
|
102
|
+
success: false,
|
|
103
|
+
error: error.message
|
|
125
104
|
});
|
|
126
105
|
}
|
|
127
106
|
});
|
|
128
107
|
|
|
129
|
-
//
|
|
130
|
-
app.post(finalConfig.routes.
|
|
108
|
+
// 2FA verification
|
|
109
|
+
app.post(finalConfig.routes.twoFactor, async (req, res) => {
|
|
131
110
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
success: false,
|
|
137
|
-
error: 'Email and password are required'
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const user = await dbOperations.getUserByEmail(email);
|
|
142
|
-
|
|
143
|
-
if (!user) {
|
|
144
|
-
return res.status(404).json({
|
|
145
|
-
success: false,
|
|
146
|
-
error: 'User not found'
|
|
147
|
-
});
|
|
148
|
-
}
|
|
111
|
+
const result = await authService.verify2FA(
|
|
112
|
+
req.body.userId,
|
|
113
|
+
req.body.code
|
|
114
|
+
);
|
|
149
115
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
116
|
+
res.json({
|
|
117
|
+
success: true,
|
|
118
|
+
...result
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
res.status(401).json({
|
|
122
|
+
success: false,
|
|
123
|
+
error: error.message
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
158
127
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
128
|
+
// Refresh token
|
|
129
|
+
app.post(finalConfig.routes.refreshToken, async (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const result = await authService.refreshToken(req.body.refreshToken);
|
|
132
|
+
res.json({
|
|
133
|
+
success: true,
|
|
134
|
+
...result
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
res.status(401).json({
|
|
138
|
+
success: false,
|
|
139
|
+
error: error.message
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
164
143
|
|
|
165
|
-
|
|
144
|
+
// Forgot password
|
|
145
|
+
app.post(finalConfig.routes.forgotPassword, async (req, res) => {
|
|
146
|
+
try {
|
|
147
|
+
await authService.forgotPassword(req.body.email);
|
|
148
|
+
res.json({
|
|
166
149
|
success: true,
|
|
167
|
-
|
|
168
|
-
user: { ...user.toJSON(), password: undefined }
|
|
150
|
+
message: 'Password reset instructions sent to your email'
|
|
169
151
|
});
|
|
170
152
|
} catch (error) {
|
|
171
|
-
res.status(400).json({
|
|
172
|
-
success: false,
|
|
173
|
-
error: error.message
|
|
153
|
+
res.status(400).json({
|
|
154
|
+
success: false,
|
|
155
|
+
error: error.message
|
|
174
156
|
});
|
|
175
157
|
}
|
|
176
158
|
});
|
|
177
159
|
|
|
178
|
-
//
|
|
179
|
-
app.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
160
|
+
// Reset password
|
|
161
|
+
app.post(finalConfig.routes.resetPassword, async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
await authService.resetPassword(
|
|
164
|
+
req.body.token,
|
|
165
|
+
req.body.newPassword
|
|
166
|
+
);
|
|
167
|
+
res.json({
|
|
168
|
+
success: true,
|
|
169
|
+
message: 'Password reset successful'
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
res.status(400).json({
|
|
173
|
+
success: false,
|
|
174
|
+
error: error.message
|
|
186
175
|
});
|
|
187
176
|
}
|
|
177
|
+
});
|
|
188
178
|
|
|
179
|
+
// Update profile (protected route)
|
|
180
|
+
app.put(finalConfig.routes.profile, verifyToken, async (req, res) => {
|
|
189
181
|
try {
|
|
190
|
-
const
|
|
191
|
-
res.json({
|
|
182
|
+
const user = await authService.updateProfile(req.user.id, req.body);
|
|
183
|
+
res.json({
|
|
192
184
|
success: true,
|
|
193
|
-
user
|
|
185
|
+
user
|
|
194
186
|
});
|
|
195
187
|
} catch (error) {
|
|
196
|
-
res.status(
|
|
197
|
-
success: false,
|
|
198
|
-
error:
|
|
188
|
+
res.status(400).json({
|
|
189
|
+
success: false,
|
|
190
|
+
error: error.message
|
|
199
191
|
});
|
|
200
192
|
}
|
|
201
193
|
});
|
|
194
|
+
|
|
195
|
+
// Social login routes
|
|
196
|
+
if (finalConfig.social.google) {
|
|
197
|
+
app.get('/auth/google',
|
|
198
|
+
passport.authenticate('google', { scope: ['profile', 'email'] })
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
app.get('/auth/google/callback',
|
|
202
|
+
passport.authenticate('google', { session: false }),
|
|
203
|
+
(req, res) => {
|
|
204
|
+
const { accessToken, refreshToken } = authService.generateTokens(req.user);
|
|
205
|
+
res.json({
|
|
206
|
+
success: true,
|
|
207
|
+
accessToken,
|
|
208
|
+
refreshToken,
|
|
209
|
+
user: req.user
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Admin-only route example
|
|
216
|
+
app.get('/admin', verifyToken, checkRole(['admin']), (req, res) => {
|
|
217
|
+
res.json({
|
|
218
|
+
success: true,
|
|
219
|
+
message: 'Admin access granted'
|
|
220
|
+
});
|
|
221
|
+
});
|
|
202
222
|
};
|
|
203
223
|
}
|
|
204
224
|
|