@dax-crafta/auth 2.1.1 → 2.1.3

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/src/index.js CHANGED
@@ -1,336 +1,442 @@
1
- const express = require('express');
2
- const passport = require('passport');
3
- const cookieParser = require('cookie-parser');
4
- const csrf = require('csurf');
5
- const helmet = require("helmet");
6
- const mongoose = require("mongoose"); // <<< FIX ADDED
7
-
8
- const GoogleStrategy = require('passport-google-oauth20').Strategy;
9
- const AuthService = require('./services/auth.service');
10
- const RoleService = require('./services/role.service');
11
- const AuditService = require('./services/audit.service');
12
- const MFAService = require('./utils/mfa');
13
- const PasswordPolicy = require('./utils/password-policy');
14
- const createAuthMiddleware = require('./middlewares/auth.middleware');
15
- const validators = require('./middlewares/validation.middleware');
16
-
17
-
18
- const defaultConfig = {
19
- strategy: 'jwt',
20
- fields: ['email', 'password'],
21
- routes: {
22
- register: '/register',
23
- login: '/login',
24
- verify: '/verify',
25
- forgotPassword: '/forgot-password',
26
- resetPassword: '/reset-password',
27
- refreshToken: '/refresh-token',
28
- profile: '/profile',
29
- twoFactor: '/2fa',
30
- roles: '/roles',
31
- permissions: '/permissions'
32
- },
33
- mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/forge-auth',
34
- maxLoginAttempts: 5,
35
- emailVerification: true,
36
- loginAlerts: true,
37
- passwordPolicy: {
38
- minLength: 8,
39
- requireUppercase: true,
40
- requireNumbers: true,
41
- requireSpecialChars: true,
42
- expiryDays: 90
43
- },
44
- smtp: null,
45
- social: {
46
- google: null,
47
- facebook: null,
48
- github: null
49
- },
50
- env: {
51
- JWT_SECRET: process.env.JWT_SECRET
52
- },
53
- accessTokenExpiry: '1h',
54
- refreshTokenDays: 7
55
- };
56
-
57
- // minimal ApiError re-export if needed
58
- class ApiError extends Error {
59
- constructor(message, status = 400) {
60
- super(message);
61
- this.status = status;
62
- }
63
- }
64
-
65
- function auth(config = {}) {
66
- const finalConfig = { ...defaultConfig, ...config };
67
- finalConfig.env = { ...(defaultConfig.env || {}), ...(config.env || process.env) };
68
-
69
- if (!finalConfig.env?.JWT_SECRET) {
70
- throw new Error('JWT_SECRET is required in environment configuration');
71
- }
72
-
73
- if (finalConfig.emailVerification && !finalConfig.smtp) {
74
- throw new Error('SMTP configuration is required for email verification');
75
- }
76
-
77
- // ------------------------------------------
78
- // ✅ FIX — Proper MongoDB Connection
79
- // ------------------------------------------
80
- if (mongoose.connection.readyState === 0) {
81
- mongoose.connect(finalConfig.mongoUrl, {
82
- useNewUrlParser: true,
83
- useUnifiedTopology: true
84
- })
85
- .then(() => console.log("📦 Auth Library MongoDB Connected"))
86
- .catch(err => console.error("❌ Auth Library MongoDB Error:", err));
87
- } else {
88
- console.log("📦 Using existing mongoose connection");
89
- }
90
- // ------------------------------------------
91
-
92
- const authService = new AuthService(finalConfig);
93
- const roleService = new RoleService();
94
- const auditService = new AuditService();
95
- const mfaService = new MFAService();
96
- const passwordPolicy = new PasswordPolicy(finalConfig.passwordPolicy);
97
- const { rateLimiter, limiterFor, verifyToken, checkRole, checkOwnershipOrAdmin } = createAuthMiddleware(finalConfig);
98
-
99
- if (finalConfig.social && finalConfig.social.google) {
100
- passport.use(new GoogleStrategy(finalConfig.social.google,
101
- async (accessToken, refreshToken, profile, done) => {
102
- try {
103
- const user = await authService.handleSocialLogin('google', profile);
104
- done(null, user);
105
- } catch (err) {
106
- done(err);
107
- }
108
- }
109
- ));
110
- }
111
-
112
- return function (app) {
113
- app.use(cookieParser());
114
-
115
- if (finalConfig.enableCSRF) {
116
- const csrfProtection = csrf({ cookie: true });
117
- app.use(csrfProtection);
118
-
119
- app.get('/csrf-token', (req, res) => {
120
- res.json({ csrfToken: req.csrfToken() });
121
- });
122
- }
123
-
124
- app.use(passport.initialize());
125
- app.use(rateLimiter);
126
- app.use(helmet({
127
- contentSecurityPolicy: false,
128
- crossOriginEmbedderPolicy: false,
129
- }));
130
-
131
- app.authService = authService;
132
- app.roleService = roleService;
133
- app.auditService = auditService;
134
- app.mfaService = mfaService;
135
- app.passwordPolicy = passwordPolicy;
136
-
137
- // REGISTER
138
- app.post(
139
- finalConfig.routes.register,
140
- limiterFor('register'),
141
- validators.registerValidator,
142
- validators.handleValidation,
143
- async (req, res, next) => {
144
- try {
145
- const { isValid, errors } = passwordPolicy.validate(req.body.password, {
146
- email: req.body.email,
147
- name: req.body.name
148
- });
149
-
150
- if (!isValid) {
151
- return res.status(400).json({ success: false, error: 'Password policy violation', details: errors });
152
- }
153
-
154
- const user = await authService.register(req.body);
155
- await auditService.logActivity({
156
- userId: user._id,
157
- action: 'register',
158
- ipAddress: req.ip,
159
- userAgent: req.headers['user-agent'],
160
- status: 'success'
161
- });
162
-
163
- res.status(201).json({ success: true, message: 'Registration successful' });
164
- } catch (err) {
165
- next(err);
166
- }
167
- }
168
- );
169
-
170
- // LOGIN
171
- app.post(
172
- finalConfig.routes.login,
173
- limiterFor('login'),
174
- validators.loginValidator,
175
- validators.handleValidation,
176
- async (req, res, next) => {
177
- try {
178
- const deviceInfo = { browser: req.headers['user-agent'], ip: req.ip };
179
- const result = await authService.login(req.body.email, req.body.password, deviceInfo);
180
-
181
- await auditService.logActivity({
182
- userId: result.user?._id,
183
- action: 'login',
184
- ipAddress: req.ip,
185
- userAgent: req.headers['user-agent'],
186
- status: 'success'
187
- });
188
-
189
- res.json({ success: true, ...result });
190
- } catch (err) {
191
- await auditService.logActivity({
192
- action: 'login',
193
- ipAddress: req.ip,
194
- userAgent: req.headers['user-agent'],
195
- status: 'failure',
196
- details: { error: err.message }
197
- });
198
- next(err);
199
- }
200
- }
201
- );
202
-
203
- // 2FA
204
- app.post(
205
- finalConfig.routes.twoFactor,
206
- limiterFor('2fa'),
207
- validators.twoFAValidator,
208
- validators.handleValidation,
209
- async (req, res, next) => {
210
- try {
211
- const result = await authService.verify2FA(req.body.userId, req.body.code);
212
- await auditService.logActivity({ userId: result.user._id, action: '2fa_verify', status: 'success' });
213
- res.json({ success: true, ...result });
214
- } catch (err) {
215
- next(err);
216
- }
217
- }
218
- );
219
-
220
- // FORGOT PASSWORD
221
- app.post(
222
- finalConfig.routes.forgotPassword,
223
- limiterFor('forgotPassword'),
224
- validators.forgotPasswordValidator,
225
- validators.handleValidation,
226
- async (req, res, next) => {
227
- try {
228
- await authService.forgotPassword(req.body.email);
229
- res.json({ success: true, message: 'Password reset email sent if user exists' });
230
- } catch (err) {
231
- next(err);
232
- }
233
- }
234
- );
235
-
236
- // RESET PASSWORD
237
- app.post(
238
- finalConfig.routes.resetPassword,
239
- validators.resetPasswordValidator,
240
- validators.handleValidation,
241
- async (req, res, next) => {
242
- try {
243
- await authService.resetPassword(req.body.token, req.body.newPassword);
244
- await auditService.logActivity({ action: 'password_reset', status: 'success' });
245
- res.json({ success: true, message: 'Password reset successful' });
246
- } catch (err) {
247
- next(err);
248
- }
249
- }
250
- );
251
-
252
- // REFRESH TOKEN
253
- app.post(
254
- finalConfig.routes.refreshToken,
255
- validators.refreshTokenValidator,
256
- validators.handleValidation,
257
- async (req, res, next) => {
258
- try {
259
- const result = await authService.refreshToken(req.body.refreshToken);
260
- res.json({ success: true, ...result });
261
- } catch (err) {
262
- next(err);
263
- }
264
- }
265
- );
266
-
267
- // UPDATE PROFILE
268
- app.put(
269
- finalConfig.routes.profile,
270
- verifyToken,
271
- checkOwnershipOrAdmin((req) => req.user.id),
272
- validators.profileUpdateValidator,
273
- validators.handleValidation,
274
- async (req, res, next) => {
275
- try {
276
- const user = await authService.updateProfile(req.user.id, req.body);
277
- await auditService.logActivity({ userId: user._id, action: 'profile_update', status: 'success' });
278
- res.json({ success: true, user });
279
- } catch (err) {
280
- next(err);
281
- }
282
- }
283
- );
284
-
285
- // CREATE ROLE (ADMIN)
286
- app.post('/roles', verifyToken, checkRole(['admin']), async (req, res, next) => {
287
- try {
288
- const role = await roleService.createRole(req.body);
289
- res.status(201).json({ success: true, role });
290
- } catch (err) {
291
- next(err);
292
- }
293
- });
294
-
295
- // GOOGLE OAUTH
296
- if (finalConfig.social && finalConfig.social.google) {
297
- app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
298
-
299
- app.get('/auth/google/callback',
300
- passport.authenticate('google', { session: false }),
301
- async (req, res, next) => {
302
- try {
303
- const tokens = await authService.generateTokens(req.user);
304
- await auditService.logActivity({
305
- userId: req.user._id,
306
- action: 'social_login',
307
- status: 'success',
308
- details: { provider: 'google' }
309
- });
310
- res.json({ success: true, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, user: req.user });
311
- } catch (err) {
312
- next(err);
313
- }
314
- });
315
- }
316
-
317
- // ERROR HANDLER
318
- app.use((err, req, res, next) => {
319
- console.error(err && err.stack ? err.stack : err);
320
- const status = err.status || (err.name === 'ValidationError' ? 400 : 500);
321
-
322
- let message;
323
- if (status >= 400 && status < 500) {
324
- message = err.message || 'Bad request';
325
- } else {
326
- message = process.env.NODE_ENV === 'development'
327
- ? (err.message || 'Internal Server Error')
328
- : 'Internal Server Error';
329
- }
330
-
331
- res.status(status).json({ success: false, error: message });
332
- });
333
- };
334
- }
335
-
336
- module.exports = { auth, ApiError };
1
+ const passport = require('passport');
2
+ const cookieParser = require('cookie-parser');
3
+ const csrf = require('csurf');
4
+ const helmet = require("helmet");
5
+ const mongoose = require("mongoose"); // <<< FIX ADDED
6
+
7
+ const GoogleStrategy = require('passport-google-oauth20').Strategy;
8
+ const AuthService = require('./services/auth.service');
9
+ const RoleService = require('./services/role.service');
10
+ const AuditService = require('./services/audit.service');
11
+ const MFAService = require('./utils/mfa');
12
+ const PasswordPolicy = require('./utils/password-policy');
13
+ const createAuthMiddleware = require('./middlewares/auth.middleware');
14
+ const validators = require('./middlewares/validation.middleware');
15
+ const { createLogger } = require('./utils/logger');
16
+ const ApiError = require('./utils/api-error');
17
+ const { runAdaptiveTests, inferFeatureFlags, testCatalog } = require('./testing/adaptive-runner');
18
+
19
+
20
+ const defaultConfig = {
21
+ strategy: 'jwt',
22
+ fields: ['email', 'password'],
23
+ routes: {
24
+ register: '/register',
25
+ login: '/login',
26
+ verify: '/verify',
27
+ forgotPassword: '/forgot-password',
28
+ resetPassword: '/reset-password',
29
+ refreshToken: '/refresh-token',
30
+ profile: '/profile',
31
+ twoFactor: '/2fa',
32
+ roles: '/roles',
33
+ permissions: '/permissions'
34
+ },
35
+ mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/forge-auth',
36
+ maxLoginAttempts: 5,
37
+ emailVerification: true,
38
+ loginAlerts: true,
39
+ passwordPolicy: {
40
+ minLength: 8,
41
+ requireUppercase: true,
42
+ requireNumbers: true,
43
+ requireSpecialChars: true,
44
+ expiryDays: 90
45
+ },
46
+ smtp: null,
47
+ social: {
48
+ google: null,
49
+ facebook: null,
50
+ github: null
51
+ },
52
+ features: {
53
+ emailVerification: true,
54
+ loginAlerts: true,
55
+ securityAttempts: true,
56
+ rateLimit: true,
57
+ auditLogs: true,
58
+ twoFactor: true,
59
+ csrf: false
60
+ },
61
+ env: {
62
+ JWT_SECRET: process.env.JWT_SECRET || 'crafta-auth-dev-secret'
63
+ },
64
+ accessTokenExpiry: '1h',
65
+ refreshTokenDays: 7
66
+ };
67
+
68
+ function auth(config = {}) {
69
+ const finalConfig = {
70
+ ...defaultConfig,
71
+ ...config,
72
+ routes: { ...defaultConfig.routes, ...(config.routes || {}) },
73
+ passwordPolicy: { ...defaultConfig.passwordPolicy, ...(config.passwordPolicy || {}) },
74
+ social: { ...defaultConfig.social, ...(config.social || {}) },
75
+ limits: { ...(config.limits || {}) },
76
+ features: { ...defaultConfig.features, ...(config.features || {}) }
77
+ };
78
+
79
+ finalConfig.env = { ...(defaultConfig.env || {}), ...(config.env || process.env) };
80
+ const logger = createLogger(finalConfig.logging !== false);
81
+
82
+ const featureBools = {
83
+ emailVerification: config.emailVerification,
84
+ loginAlerts: config.loginAlerts,
85
+ csrf: config.enableCSRF
86
+ };
87
+
88
+ Object.keys(featureBools).forEach((key) => {
89
+ if (typeof featureBools[key] === 'boolean') {
90
+ finalConfig.features[key] = featureBools[key];
91
+ }
92
+ });
93
+
94
+ finalConfig.emailVerification = finalConfig.features.emailVerification;
95
+ finalConfig.loginAlerts = finalConfig.features.loginAlerts;
96
+ finalConfig.enableCSRF = finalConfig.features.csrf;
97
+
98
+ if (finalConfig.features.securityAttempts === false) {
99
+ finalConfig.loginAlerts = false;
100
+ finalConfig.features.loginAlerts = false;
101
+ }
102
+
103
+ if (finalConfig.env?.JWT_SECRET === 'crafta-auth-dev-secret') {
104
+ logger.logWarn('JWT_SECRET not provided; using development default secret. Set env.JWT_SECRET in production.');
105
+ }
106
+
107
+ const smtpEnabled = !!(
108
+ finalConfig.smtp &&
109
+ finalConfig.smtp.host &&
110
+ finalConfig.smtp.port &&
111
+ finalConfig.smtp.auth &&
112
+ finalConfig.smtp.auth.user &&
113
+ finalConfig.smtp.auth.pass &&
114
+ finalConfig.smtp.from
115
+ );
116
+
117
+ if (!smtpEnabled && finalConfig.features.emailVerification) {
118
+ logger.logWarn('SMTP not configured; emailVerification auto-disabled.');
119
+ finalConfig.features.emailVerification = false;
120
+ finalConfig.emailVerification = false;
121
+ }
122
+
123
+ if (!smtpEnabled && finalConfig.features.loginAlerts) {
124
+ logger.logWarn('SMTP not configured; loginAlerts auto-disabled.');
125
+ finalConfig.features.loginAlerts = false;
126
+ finalConfig.loginAlerts = false;
127
+ }
128
+
129
+ // ------------------------------------------
130
+ // ✅ Proper MongoDB Connection
131
+ // ------------------------------------------
132
+ if (mongoose.connection.readyState === 0) {
133
+ mongoose.connect(finalConfig.mongoUrl, {
134
+ useNewUrlParser: true,
135
+ useUnifiedTopology: true
136
+ })
137
+ .then(() => logger.logSuccess("MongoDB connected"))
138
+ .catch(err => logger.logError(`MongoDB connection error: ${err.message}`));
139
+ } else {
140
+ logger.logInfo("Using existing mongoose connection");
141
+ }
142
+ // ------------------------------------------
143
+
144
+ const auditService = finalConfig.features.auditLogs ? new AuditService() : null;
145
+ const mfaService = new MFAService();
146
+
147
+ // Pass shared services into AuthService config so it can log/audit correctly
148
+ const authService = new AuthService({
149
+ ...finalConfig,
150
+ auditService,
151
+ mfaService
152
+ });
153
+ const roleService = new RoleService();
154
+ const passwordPolicy = new PasswordPolicy(finalConfig.passwordPolicy);
155
+ const { rateLimiter, limiterFor, verifyToken, checkRole, checkOwnershipOrAdmin } = createAuthMiddleware(finalConfig);
156
+
157
+ if (finalConfig.social && finalConfig.social.google) {
158
+ passport.use(new GoogleStrategy(finalConfig.social.google,
159
+ async (accessToken, refreshToken, profile, done) => {
160
+ try {
161
+ const user = await authService.handleSocialLogin('google', profile);
162
+ done(null, user);
163
+ } catch (err) {
164
+ done(err);
165
+ }
166
+ }
167
+ ));
168
+ }
169
+
170
+ return function (app) {
171
+ const safeAudit = async (entry) => {
172
+ if (!auditService) return;
173
+ try {
174
+ await auditService.logActivity(entry);
175
+ } catch (err) {
176
+ logger.logWarn(`Audit logging failed: ${err.message}`);
177
+ }
178
+ };
179
+
180
+ app.use(cookieParser());
181
+
182
+ if (finalConfig.enableCSRF) {
183
+ const csrfProtection = csrf({ cookie: true });
184
+ app.use(csrfProtection);
185
+
186
+ app.get('/csrf-token', (req, res) => {
187
+ res.json({ csrfToken: req.csrfToken() });
188
+ });
189
+ }
190
+
191
+ app.use(passport.initialize());
192
+ app.use(rateLimiter);
193
+ app.use(helmet({
194
+ contentSecurityPolicy: false,
195
+ crossOriginEmbedderPolicy: false,
196
+ }));
197
+
198
+ app.authService = authService;
199
+ app.roleService = roleService;
200
+ app.auditService = auditService;
201
+ app.mfaService = mfaService;
202
+ app.passwordPolicy = passwordPolicy;
203
+ app.authLogger = logger;
204
+
205
+ // EMAIL VERIFICATION
206
+ app.get(
207
+ finalConfig.routes.verify,
208
+ async (req, res, next) => {
209
+ try {
210
+ const token = req.query.token;
211
+ const user = await authService.verifyEmail(token);
212
+
213
+ res.json({
214
+ success: true,
215
+ message: 'Email verified successfully',
216
+ userId: user._id
217
+ });
218
+ } catch (err) {
219
+ next(err);
220
+ }
221
+ }
222
+ );
223
+
224
+ // REGISTER
225
+ app.post(
226
+ finalConfig.routes.register,
227
+ limiterFor('register'),
228
+ validators.registerValidator,
229
+ validators.handleValidation,
230
+ async (req, res, next) => {
231
+ try {
232
+ const { isValid, errors } = passwordPolicy.validate(req.body.password, {
233
+ email: req.body.email,
234
+ name: req.body.name
235
+ });
236
+
237
+ if (!isValid) {
238
+ return res.status(400).json({ success: false, error: 'Password policy violation', details: errors });
239
+ }
240
+
241
+ const user = await authService.register(req.body);
242
+ await safeAudit({
243
+ userId: user._id,
244
+ action: 'register',
245
+ ipAddress: req.ip,
246
+ userAgent: req.headers['user-agent'],
247
+ status: 'success'
248
+ });
249
+
250
+ logger.logSuccess('User registered');
251
+ res.status(201).json({ success: true, message: 'Registration successful' });
252
+ } catch (err) {
253
+ logger.logError(`Register failed: ${err.message}`);
254
+ next(err);
255
+ }
256
+ }
257
+ );
258
+
259
+ // LOGIN
260
+ app.post(
261
+ finalConfig.routes.login,
262
+ limiterFor('login'),
263
+ validators.loginValidator,
264
+ validators.handleValidation,
265
+ async (req, res, next) => {
266
+ try {
267
+ const deviceInfo = { browser: req.headers['user-agent'], ip: req.ip };
268
+ const result = await authService.login(req.body.email, req.body.password, deviceInfo);
269
+
270
+ await safeAudit({
271
+ userId: result.user?._id,
272
+ action: 'login',
273
+ ipAddress: req.ip,
274
+ userAgent: req.headers['user-agent'],
275
+ status: 'success'
276
+ });
277
+
278
+ logger.logSuccess('Login successful');
279
+ res.json({ success: true, ...result });
280
+ } catch (err) {
281
+ await safeAudit({
282
+ action: 'login',
283
+ ipAddress: req.ip,
284
+ userAgent: req.headers['user-agent'],
285
+ status: 'failure',
286
+ details: { error: err.message }
287
+ });
288
+ logger.logError(`Login failed: ${err.message}`);
289
+ next(err);
290
+ }
291
+ }
292
+ );
293
+
294
+ // 2FA
295
+ if (finalConfig.features.twoFactor !== false) {
296
+ app.post(
297
+ finalConfig.routes.twoFactor,
298
+ limiterFor('2fa'),
299
+ validators.twoFAValidator,
300
+ validators.handleValidation,
301
+ async (req, res, next) => {
302
+ try {
303
+ const result = await authService.verify2FA(req.body.userId, req.body.code);
304
+ await safeAudit({ userId: result.user._id, action: '2fa_verify', status: 'success' });
305
+ logger.logSuccess('2FA verified');
306
+ res.json({ success: true, ...result });
307
+ } catch (err) {
308
+ logger.logError(`2FA failed: ${err.message}`);
309
+ next(err);
310
+ }
311
+ }
312
+ );
313
+ }
314
+
315
+ // FORGOT PASSWORD
316
+ app.post(
317
+ finalConfig.routes.forgotPassword,
318
+ limiterFor('forgotPassword'),
319
+ validators.forgotPasswordValidator,
320
+ validators.handleValidation,
321
+ async (req, res, next) => {
322
+ try {
323
+ await authService.forgotPassword(req.body.email);
324
+ logger.logInfo('Password reset email triggered');
325
+ res.json({ success: true, message: 'Password reset email sent if user exists' });
326
+ } catch (err) {
327
+ logger.logError(`Forgot password failed: ${err.message}`);
328
+ next(err);
329
+ }
330
+ }
331
+ );
332
+
333
+ // RESET PASSWORD
334
+ app.post(
335
+ finalConfig.routes.resetPassword,
336
+ validators.resetPasswordValidator,
337
+ validators.handleValidation,
338
+ async (req, res, next) => {
339
+ try {
340
+ await authService.resetPassword(req.body.token, req.body.newPassword);
341
+ await safeAudit({ action: 'password_reset', status: 'success' });
342
+ logger.logSuccess('Password reset completed');
343
+ res.json({ success: true, message: 'Password reset successful' });
344
+ } catch (err) {
345
+ logger.logError(`Reset password failed: ${err.message}`);
346
+ next(err);
347
+ }
348
+ }
349
+ );
350
+
351
+ // REFRESH TOKEN
352
+ app.post(
353
+ finalConfig.routes.refreshToken,
354
+ validators.refreshTokenValidator,
355
+ validators.handleValidation,
356
+ async (req, res, next) => {
357
+ try {
358
+ const result = await authService.refreshToken(req.body.refreshToken);
359
+ logger.logSuccess('Token refreshed');
360
+ res.json({ success: true, ...result });
361
+ } catch (err) {
362
+ logger.logError(`Refresh token failed: ${err.message}`);
363
+ next(err);
364
+ }
365
+ }
366
+ );
367
+
368
+ // UPDATE PROFILE
369
+ app.put(
370
+ finalConfig.routes.profile,
371
+ verifyToken,
372
+ checkOwnershipOrAdmin((req) => req.user.id),
373
+ validators.profileUpdateValidator,
374
+ validators.handleValidation,
375
+ async (req, res, next) => {
376
+ try {
377
+ const user = await authService.updateProfile(req.user.id, req.body);
378
+ await safeAudit({ userId: user._id, action: 'profile_update', status: 'success' });
379
+ logger.logSuccess('Profile updated');
380
+ res.json({ success: true, user });
381
+ } catch (err) {
382
+ logger.logError(`Profile update failed: ${err.message}`);
383
+ next(err);
384
+ }
385
+ }
386
+ );
387
+
388
+ // CREATE ROLE (ADMIN)
389
+ app.post('/roles', verifyToken, checkRole(['admin']), async (req, res, next) => {
390
+ try {
391
+ const role = await roleService.createRole(req.body);
392
+ res.status(201).json({ success: true, role });
393
+ } catch (err) {
394
+ next(err);
395
+ }
396
+ });
397
+
398
+ // GOOGLE OAUTH
399
+ if (finalConfig.social && finalConfig.social.google) {
400
+ app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
401
+
402
+ app.get('/auth/google/callback',
403
+ passport.authenticate('google', { session: false }),
404
+ async (req, res, next) => {
405
+ try {
406
+ const tokens = await authService.generateTokens(req.user);
407
+ await safeAudit({
408
+ userId: req.user._id,
409
+ action: 'social_login',
410
+ status: 'success',
411
+ details: { provider: 'google' }
412
+ });
413
+ logger.logSuccess('Google OAuth success');
414
+ res.json({ success: true, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, user: req.user });
415
+ } catch (err) {
416
+ logger.logError(`Google OAuth failed: ${err.message}`);
417
+ next(err);
418
+ }
419
+ });
420
+ }
421
+
422
+ // ERROR HANDLER
423
+ app.use((err, req, res, next) => {
424
+ console.error(err && err.stack ? err.stack : err);
425
+ const status = err.status || (err.name === 'ValidationError' ? 400 : 500);
426
+
427
+ let message;
428
+ if (status >= 400 && status < 500) {
429
+ message = err.message || 'Bad request';
430
+ } else {
431
+ message = process.env.NODE_ENV === 'development'
432
+ ? (err.message || 'Internal Server Error')
433
+ : 'Internal Server Error';
434
+ }
435
+
436
+ logger.logError(`Error ${status}: ${message}`);
437
+ res.status(status).json({ success: false, error: message });
438
+ });
439
+ };
440
+ }
441
+
442
+ module.exports = { auth, ApiError, runAdaptiveTests, inferFeatureFlags, testCatalog };