@dax-crafta/auth 2.1.7 → 2.1.8

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/.mocharc.cjs ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ spec: 'test/*.test.js',
3
+ timeout: 30000,
4
+ reporter: 'spec',
5
+ require: [],
6
+ };
package/README.md CHANGED
@@ -1,274 +1,167 @@
1
- # @dax-crafta/auth
2
-
3
- Production-ready auth for Crafta apps with plug-and-play defaults and clean JSON customization.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@dax-crafta/auth.svg)](https://www.npmjs.com/package/@dax-crafta/auth)
6
- [![License](https://img.shields.io/npm/l/@dax-crafta/auth.svg)](https://github.com/daxp472/crafta/blob/main/LICENSE)
7
- [![Downloads](https://img.shields.io/npm/dm/@dax-crafta/auth.svg)](https://www.npmjs.com/package/@dax-crafta/auth)
8
-
9
- ## Why this package
10
-
11
- - You should not rebuild auth from scratch for every project.
12
- - This package gives you login, refresh tokens, password reset, RBAC, 2FA, audit logs, and feature toggles out of the box.
13
- - Keep defaults for speed, customize only where needed.
14
-
15
- ## What you get
16
-
17
- - JWT auth with refresh token rotation
18
- - Register, login, verify, forgot/reset password routes
19
- - Optional 2FA (TOTP + backup codes)
20
- - RBAC with role and permission service
21
- - Route-level rate limiting
22
- - Password policy and password history checks
23
- - Optional Google OAuth
24
- - Audit logs (Mongo + file)
25
-
26
- ## 1. Install
27
-
28
- ```bash
29
- npm install crafta @dax-crafta/auth
30
- ```
31
-
32
- ## 2. Minimal setup (copy-paste)
33
-
34
- Create `server.js`:
35
-
36
- ```js
37
- const { crafta } = require('crafta');
38
- const { auth } = require('@dax-crafta/auth');
39
-
40
- const app = crafta();
41
-
42
- auth({
43
- env: {
44
- JWT_SECRET: process.env.JWT_SECRET || 'dev-secret-123'
45
- },
46
- mongoUrl: process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/crafta-auth-demo',
47
-
48
- // Local dev ke liye simple mode
49
- features: {
50
- emailVerification: false,
51
- loginAlerts: false,
52
- csrf: false
53
- }
54
- })(app);
55
-
56
- app.get('/health', (req, res) => {
57
- res.json({ ok: true, message: 'Auth is running' });
58
- });
59
-
60
- app.listen(3000, () => {
61
- console.log('Server running on http://localhost:3000');
62
- });
63
- ```
64
-
65
- Run it:
66
-
67
- ```bash
68
- node server.js
69
- ```
70
-
71
- ## 3. First Postman test flow
72
-
73
- ### Health
74
- - `GET /health`
75
-
76
- ### Register
77
- - `POST /register`
78
-
79
- ```json
80
- {
81
- "name": "Test User",
82
- "email": "testuser1@example.com",
83
- "password": "Strong@1234"
84
- }
85
- ```
86
-
87
- ### Login
88
- - `POST /login`
89
-
90
- ```json
91
- {
92
- "email": "testuser1@example.com",
93
- "password": "Strong@1234"
94
- }
95
- ```
96
-
97
- Use returned `accessToken` on protected routes:
98
- - `Authorization: Bearer <token>`
99
-
100
- ### Profile (protected)
101
- - `PUT /profile`
102
-
103
- ```json
104
- {
105
- "name": "Updated Name"
106
- }
107
- ```
108
-
109
- ### Refresh token
110
- - `POST /refresh-token`
111
-
112
- ```json
113
- {
114
- "refreshToken": "<token-from-login>"
115
- }
116
- ```
117
-
118
- ## 4. Customize with JSON (main power)
119
-
120
- Everything is controlled from one config object.
121
-
122
- ```js
123
- auth({
124
- strategy: 'jwt',
125
- fields: ['name', 'email', 'password', 'age'],
126
-
127
- routes: {
128
- register: '/api/auth/register',
129
- login: '/api/auth/login',
130
- verify: '/api/auth/verify',
131
- forgotPassword: '/api/auth/forgot-password',
132
- resetPassword: '/api/auth/reset-password',
133
- refreshToken: '/api/auth/refresh-token',
134
- profile: '/api/auth/profile',
135
- twoFactor: '/api/auth/2fa'
136
- },
137
-
138
- passwordPolicy: {
139
- minLength: 10,
140
- requireUppercase: true,
141
- requireLowercase: true,
142
- requireNumbers: true,
143
- requireSpecialChars: true,
144
- expiryDays: 90,
145
- minStrength: 3
146
- },
147
-
148
- limits: {
149
- loginMax: 10,
150
- twoFAMax: 20,
151
- forgotPasswordMax: 5,
152
- refreshMax: 30
153
- }
154
- })(app);
155
- ```
156
-
157
- ## 5. Feature toggles (fast on/off)
158
-
159
- Set any feature to `false` and it turns off.
160
-
161
- ```js
162
- auth({
163
- features: {
164
- emailVerification: false,
165
- loginAlerts: false,
166
- securityAttempts: true,
167
- rateLimit: true,
168
- auditLogs: true,
169
- twoFactor: true,
170
- csrf: false
171
- }
172
- })(app);
173
- ```
174
-
175
- Notes:
176
- - If SMTP is missing, emailVerification/loginAlerts are auto-disabled with warning.
177
- - If `securityAttempts: false`, lockout attempt handling is disabled.
178
-
179
- ## 6. Email setup (optional)
180
-
181
- Enable this only when SMTP is ready:
182
-
183
- ```js
184
- auth({
185
- features: {
186
- emailVerification: true,
187
- loginAlerts: true
188
- },
189
- smtp: {
190
- host: process.env.SMTP_HOST,
191
- port: Number(process.env.SMTP_PORT || 587),
192
- auth: {
193
- user: process.env.SMTP_USER,
194
- pass: process.env.SMTP_PASS
195
- },
196
- from: process.env.SMTP_FROM
197
- },
198
- baseUrl: process.env.BASE_URL || 'http://localhost:3000'
199
- })(app);
200
- ```
201
-
202
- ## 7. Google OAuth setup (optional)
203
-
204
- ```js
205
- auth({
206
- social: {
207
- google: {
208
- clientID: process.env.GOOGLE_CLIENT_ID,
209
- clientSecret: process.env.GOOGLE_CLIENT_SECRET,
210
- callbackURL: 'http://localhost:3000/auth/google/callback'
211
- }
212
- }
213
- })(app);
214
- ```
215
-
216
- Routes:
217
- - `GET /auth/google`
218
- - `GET /auth/google/callback`
219
-
220
- ## 8. RBAC quick use
221
-
222
- ```js
223
- const role = await app.roleService.createRole({
224
- name: 'admin',
225
- permissions: [
226
- { resource: 'users', actions: ['create', 'read', 'update', 'delete'] }
227
- ]
228
- });
229
-
230
- const allowed = await app.roleService.checkPermission('admin', 'users', 'delete');
231
- ```
232
-
233
- ## 9. Environment variables
234
-
235
- ```bash
236
- JWT_SECRET=my-super-secret
237
- MONGO_URL=mongodb://127.0.0.1:27017/crafta-auth-demo
238
- SMTP_HOST=smtp.example.com
239
- SMTP_PORT=587
240
- SMTP_USER=you@example.com
241
- SMTP_PASS=your-password
242
- SMTP_FROM=noreply@example.com
243
- BASE_URL=http://localhost:3000
244
- GOOGLE_CLIENT_ID=...
245
- GOOGLE_CLIENT_SECRET=...
246
- ```
247
-
248
- ## 10. Troubleshooting
249
-
250
- - 401 on protected route
251
- - Token missing or expired.
252
- - Send `Authorization: Bearer <accessToken>`.
253
-
254
- - Register fails on password policy
255
- - Use uppercase + lowercase + number + special char.
256
-
257
- - Email not sending
258
- - Check SMTP credentials and port.
259
- - If SMTP absent, email features auto-disable by design.
260
-
261
- - Refresh token invalid
262
- - Refresh token is rotated; use latest token returned by API.
263
-
264
- ## 11. Release confidence
265
-
266
- - Built-in smoke suite is available:
267
-
268
- ```bash
269
- npm test
270
- ```
271
-
272
- ## License
273
-
274
- MIT © Dax Crafta
1
+ # @dax-crafta/auth
2
+
3
+ Production-ready authentication library for Crafta apps, featuring plug-and-play defaults, robust security, and clean JSON-based customization.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@dax-crafta/auth.svg)](https://www.npmjs.com/package/@dax-crafta/auth)
6
+ [![License](https://img.shields.io/npm/l/@dax-crafta/auth.svg)](https://github.com/NPM-crafta/crafta-auth/blob/main/LICENSE)
7
+
8
+ ## Features
9
+ 1. **JWT Authentication**: Secure sessions with automatic refresh token rotation.
10
+ 2. **Standard Routes**: Built-in register, login, verify email, forgot/reset password, and refresh routes.
11
+ 3. **Multi-Factor Authentication (MFA)**: Optional TOTP-based 2FA with backup codes support.
12
+ 4. **RBAC**: Role-Based Access Control service to manage permissions and roles.
13
+ 5. **Rate Limiting**: Route-level protection for sensitive endpoints (login, register, 2FA).
14
+ 6. **Password Policy**: Strict policy enforcement with password history checks and strength validation.
15
+ 7. **Google OAuth**: Optional social login integration.
16
+ 8. **Audit Logs**: Comprehensive logging to MongoDB and local files.
17
+ 9. **Session Management**: Full session control with per-user session limits and global logout.
18
+ 10. **Webhooks**: Real-time event notifications for auth lifecycle events (registered, login, logout, password change).
19
+ 11. **Passwordless Login**: Magic link authentication.
20
+ 12. **Standardized Error Handling**: Unified error format with 19 distinct `AUTH_*` codes.
21
+ 13. **CORS Middleware**: Explicit origin whitelisting for secure cross-origin requests.
22
+ 14. **Customizable Fields**: Flexible user model customization.
23
+
24
+ ## Install
25
+ ```bash
26
+ npm install @dax-crafta/auth
27
+ ```
28
+
29
+ ## Minimal Setup
30
+ ```javascript
31
+ const { crafta } = require('crafta');
32
+ const { auth } = require('@dax-crafta/auth');
33
+
34
+ const app = crafta();
35
+
36
+ auth({
37
+ env: {
38
+ JWT_SECRET: process.env.JWT_SECRET || 'dev-secret-123'
39
+ },
40
+ mongoUrl: process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/crafta-auth-demo'
41
+ })(app);
42
+
43
+ app.listen(3000, () => console.log('Server running on http://localhost:3000'));
44
+ ```
45
+
46
+ ## Configuration Options
47
+ The library is configured via a single object passed to the `auth()` function:
48
+
49
+ ```javascript
50
+ auth({
51
+ strategy: 'jwt', // Currently defaults to 'jwt'
52
+ fields: ['name', 'email', 'password'],
53
+ routes: { /* Custom route paths */ },
54
+ passwordPolicy: {
55
+ minLength: 8,
56
+ requireUppercase: true,
57
+ requireNumbers: true,
58
+ requireSpecialChars: true,
59
+ expiryDays: 90
60
+ },
61
+ limits: {
62
+ loginMax: 10,
63
+ registerMax: 20
64
+ }
65
+ })(app);
66
+ ```
67
+
68
+ ## Feature Toggles
69
+ Enable or disable features via the `features` object:
70
+
71
+ ```javascript
72
+ auth({
73
+ features: {
74
+ emailVerification: true,
75
+ twoFactor: true,
76
+ passwordless: false,
77
+ auditLogs: true,
78
+ webhooks: false,
79
+ rateLimit: true,
80
+ csrf: false
81
+ }
82
+ })(app);
83
+ ```
84
+
85
+ ## Email Setup
86
+ Configure SMTP to enable email-based features (Verification, Forgot Password, Magic Links, Login Alerts):
87
+
88
+ ```javascript
89
+ auth({
90
+ smtp: {
91
+ host: process.env.SMTP_HOST,
92
+ port: 587,
93
+ auth: {
94
+ user: process.env.SMTP_USER,
95
+ pass: process.env.SMTP_PASS
96
+ },
97
+ from: process.env.SMTP_FROM
98
+ },
99
+ baseUrl: 'http://localhost:3000'
100
+ })(app);
101
+ ```
102
+
103
+ ## Google OAuth Setup
104
+ ```javascript
105
+ auth({
106
+ social: {
107
+ google: {
108
+ clientID: process.env.GOOGLE_CLIENT_ID,
109
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
110
+ callbackURL: 'http://localhost:3000/auth/google/callback'
111
+ }
112
+ }
113
+ })(app);
114
+ ```
115
+
116
+ ## RBAC Quick Use
117
+ Access the role service directly from your app instance:
118
+
119
+ ```javascript
120
+ // Check permission
121
+ const allowed = await app.roleService.checkPermission('admin', 'users', 'delete');
122
+
123
+ // Create role
124
+ await app.roleService.createRole({
125
+ name: 'editor',
126
+ permissions: [{ resource: 'posts', actions: ['create', 'update'] }]
127
+ });
128
+ ```
129
+
130
+ ## Environment Variables
131
+ - `JWT_SECRET`: Secret key for signing tokens.
132
+ - `MONGO_URL`: MongoDB connection string.
133
+ - `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`: SMTP configuration.
134
+ - `BASE_URL`: Base URL for email links.
135
+ - `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`: Google OAuth credentials.
136
+
137
+ ## Troubleshooting
138
+ - **401 Unauthorized**: Check if `Authorization: Bearer <accessToken>` header is present and valid.
139
+ - **Password policy errors**: Ensure passwords meet length, uppercase, number, and special character requirements.
140
+ - **Email issues**: Verify SMTP configuration; features relying on email will automatically disable if SMTP is misconfigured.
141
+
142
+ ## Error Codes
143
+ | Code | Description | HTTP Status |
144
+ | :--- | :--- | :--- |
145
+ | `AUTH_BAD_REQUEST` | Generic bad request error | 400 |
146
+ | `AUTH_VALIDATION` | Invalid request data | 400 |
147
+ | `AUTH_PASSWORD_POLICY` | Password policy violation | 400 |
148
+ | `AUTH_PASSWORD_REUSE` | Cannot reuse previous passwords | 400 |
149
+ | `AUTH_SOCIAL_FAILED` | Social login failure | 400 |
150
+ | `AUTH_INVALID_ID` | Invalid resource identifier | 400 |
151
+ | `AUTH_UNAUTHORIZED` | Authentication required | 401 |
152
+ | `AUTH_INVALID_CREDENTIALS` | Invalid email or password | 401 |
153
+ | `AUTH_INVALID_TOKEN` | Token invalid or missing | 401 |
154
+ | `AUTH_EXPIRED_TOKEN` | Token expired | 401 |
155
+ | `AUTH_2FA_FAILED` | Invalid 2FA code | 401 |
156
+ | `AUTH_FORBIDDEN` | Insufficient permissions | 403 |
157
+ | `AUTH_ACCOUNT_LOCKED` | Account locked due to failed attempts | 403 |
158
+ | `AUTH_EMAIL_NOT_VERIFIED` | Email verification required | 403 |
159
+ | `AUTH_2FA_REQUIRED` | 2FA code input required | 403 |
160
+ | `AUTH_NOT_FOUND` | Resource not found | 404 |
161
+ | `AUTH_CONFLICT` | Resource already exists | 409 |
162
+ | `AUTH_RATE_LIMITED` | Too many requests | 429 |
163
+ | `AUTH_SMTP_DISABLED` | Email delivery service disabled | 503 |
164
+ | `AUTH_INTERNAL` | Unexpected internal error | 500 |
165
+
166
+ ## License
167
+ MIT © Dax Crafta
package/image.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dax-crafta/auth",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "A powerful, flexible, and secure authentication plugin for the Crafta framework. Supports JWT, social login, 2FA, RBAC, audit logging, and enterprise-grade security features.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -61,5 +61,9 @@
61
61
  },
62
62
  "homepage": "https://github.com/daxp472/crafta#readme",
63
63
  "license": "MIT",
64
- "author": "Dax Patel"
64
+ "author": "Dax Patel",
65
+ "devDependencies": {
66
+ "@types/mocha": "^10.0.10",
67
+ "mocha": "^10.8.2"
68
+ }
65
69
  }
package/src/index.d.ts CHANGED
@@ -56,6 +56,8 @@ export interface RateLimitConfig {
56
56
  forgotPasswordMax?: number;
57
57
  refreshMax?: number;
58
58
  registerMax?: number;
59
+ /** Per-route max overrides keyed by route name (e.g. "login", "register", "2fa") */
60
+ [routeName: string]: number | undefined;
59
61
  }
60
62
 
61
63
  export interface WebhookConfig {
@@ -67,6 +69,8 @@ export interface WebhookConfig {
67
69
  export interface SessionConfig {
68
70
  maxSessions?: number;
69
71
  sessionExpiryMs?: number;
72
+ /** Milliseconds before expiry at which the X-Session-Expires-In warning header is emitted. Default: 300000 (5 min). */
73
+ warningThresholdMs?: number;
70
74
  }
71
75
 
72
76
  export interface LockoutConfig {
@@ -183,11 +187,14 @@ export interface MFAService {
183
187
  }
184
188
 
185
189
  export interface PasswordPolicyService {
186
- validate(password: string, userInfo?: any): { isValid: boolean; errors: string[]; strength: number };
190
+ validate(password: string, userInfo?: any): { isValid: boolean; errors: string[]; strength: number; strengthScore: number };
187
191
  allowsReuse(password: string, passwordHistory: any[], bcrypt: any): Promise<boolean>;
188
192
  isExpired(lastChange: Date): boolean;
189
193
  }
190
194
 
195
+ /** Returns a heuristic password strength score from 0 (very weak) to 4 (very strong). */
196
+ export function getPasswordStrength(password: string): number;
197
+
191
198
  export function auth(config?: AuthConfig): (app: any) => void;
192
199
  export function normalizeError(err: unknown): ApiError;
193
200
 
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ const cookieParser = require('cookie-parser');
3
3
  const csrf = require('csurf');
4
4
  const helmet = require("helmet");
5
5
  const mongoose = require("mongoose");
6
+ const jwt = require('jsonwebtoken');
6
7
 
7
8
  const GoogleStrategy = require('passport-google-oauth20').Strategy;
8
9
  const AuthService = require('./services/auth.service');
@@ -18,6 +19,7 @@ const corsMiddleware = require('./middlewares/cors.middleware');
18
19
  const validators = require('./middlewares/validation.middleware');
19
20
  const { createLogger } = require('./utils/logger');
20
21
  const ApiError = require('./utils/api-error');
22
+ const { getPasswordStrength } = require('./utils/password-strength');
21
23
  const { runAdaptiveTests, inferFeatureFlags, testCatalog } = require('./testing/adaptive-runner');
22
24
 
23
25
 
@@ -78,7 +80,8 @@ const defaultConfig = {
78
80
  webhooks: null,
79
81
  session: {
80
82
  maxSessions: 5,
81
- sessionExpiryMs: 30 * 24 * 60 * 60 * 1000
83
+ sessionExpiryMs: 30 * 24 * 60 * 60 * 1000,
84
+ warningThresholdMs: 5 * 60 * 1000
82
85
  }
83
86
  };
84
87
 
@@ -154,13 +157,24 @@ function auth(config = {}) {
154
157
  // ------------------------------------------
155
158
  // MongoDB Connection — support external connections
156
159
  // ------------------------------------------
160
+ const connectDB = () => {
161
+ mongoose.connect(finalConfig.mongoUrl)
162
+ .then(() => logger.logSuccess("MongoDB connected"))
163
+ .catch(err => {
164
+ logger.logError(`MongoDB connection error: ${err.message}`);
165
+ setTimeout(connectDB, 5000);
166
+ });
167
+ };
168
+
157
169
  if (finalConfig.mongooseConnection) {
158
170
  // Allow host app to pass an existing mongoose connection
159
171
  logger.logInfo("Using provided mongoose connection");
160
172
  } else if (mongoose.connection.readyState === 0) {
161
- mongoose.connect(finalConfig.mongoUrl)
162
- .then(() => logger.logSuccess("MongoDB connected"))
163
- .catch(err => logger.logError(`MongoDB connection error: ${err.message}`));
173
+ connectDB();
174
+ mongoose.connection.on('disconnected', () => {
175
+ logger.logWarn("MongoDB disconnected, attempting to reconnect...");
176
+ connectDB();
177
+ });
164
178
  } else {
165
179
  logger.logInfo("Using existing mongoose connection");
166
180
  }
@@ -219,6 +233,29 @@ function auth(config = {}) {
219
233
  }
220
234
  };
221
235
 
236
+ // ------------------------------------------
237
+ // SESSION TIMEOUT WARNING MIDDLEWARE
238
+ // ------------------------------------------
239
+ const warningThresholdMs = finalConfig.session?.warningThresholdMs || 5 * 60 * 1000;
240
+ app.use((req, res, next) => {
241
+ const authHeader = req.headers.authorization;
242
+ if (authHeader && authHeader.startsWith('Bearer ')) {
243
+ const token = authHeader.split(' ')[1];
244
+ try {
245
+ const decoded = jwt.decode(token);
246
+ if (decoded && decoded.exp) {
247
+ const timeLeft = decoded.exp * 1000 - Date.now();
248
+ if (timeLeft > 0 && timeLeft < warningThresholdMs) {
249
+ res.setHeader('X-Session-Expires-In', Math.floor(timeLeft / 1000));
250
+ }
251
+ }
252
+ } catch (err) {
253
+ // Ignore decoding errors in middleware
254
+ }
255
+ }
256
+ next();
257
+ });
258
+
222
259
  app.use(cookieParser());
223
260
 
224
261
  // CORS — applied before routes
@@ -253,14 +290,45 @@ function auth(config = {}) {
253
290
  // ------------------------------------------
254
291
  // HEALTH CHECK
255
292
  // ------------------------------------------
256
- app.get('/auth/health', (req, res) => {
293
+ app.get('/auth/health', async (req, res) => {
257
294
  const dbState = mongoose.connection.readyState;
258
295
  const dbStatus = dbState === 1 ? 'connected' : dbState === 2 ? 'connecting' : 'disconnected';
296
+
297
+ // Get active session count
298
+ let activeSessions = 0;
299
+ if (mongoose.connection.readyState === 1) {
300
+ try {
301
+ activeSessions = await mongoose.connection.db.collection('users').aggregate([
302
+ { $match: { sessions: { $exists: true, $not: { $size: 0 } } } },
303
+ { $unwind: '$sessions' },
304
+ { $match: { 'sessions.createdAt': { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } } },
305
+ { $group: { _id: '$_id', count: { $sum: 1 } } },
306
+ { $group: { _id: null, total: { $sum: '$count' } } }
307
+ ]).toArray();
308
+ activeSessions = activeSessions[0]?.total || 0;
309
+ } catch (err) {
310
+ logger.logWarn(`Failed to count active sessions: ${err.message}`);
311
+ }
312
+ }
313
+
314
+ // Get memory usage
315
+ const memoryUsage = process.memoryUsage();
316
+
317
+ // Get uptime
318
+ const uptime = process.uptime();
319
+
259
320
  res.json({
260
321
  success: true,
261
322
  status: 'ok',
262
323
  db: dbStatus,
263
324
  version: require('../package.json').version,
325
+ uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`,
326
+ activeSessions,
327
+ memoryUsage: {
328
+ rss: `${Math.round(memoryUsage.rss / 1024 / 1024)}MB`,
329
+ heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)}MB`,
330
+ heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`
331
+ },
264
332
  features: {
265
333
  emailVerification: finalConfig.features.emailVerification,
266
334
  twoFactor: finalConfig.features.twoFactor,
@@ -482,7 +550,7 @@ function auth(config = {}) {
482
550
  verifyToken,
483
551
  async (req, res, next) => {
484
552
  try {
485
- await authService.logout(req.user.id, req.body.sessionId);
553
+ await authService.logout(req.user.id, req.body.sessionId, req.body.refreshToken);
486
554
  await safeAudit({ userId: req.user.id, action: 'logout', status: 'success' });
487
555
  await safeWebhook('user.logout', { userId: req.user.id });
488
556
  res.json({ success: true, message: 'Logged out successfully' });
@@ -598,4 +666,4 @@ function auth(config = {}) {
598
666
  };
599
667
  }
600
668
 
601
- module.exports = { auth, ApiError, runAdaptiveTests, inferFeatureFlags, testCatalog };
669
+ module.exports = { auth, ApiError, getPasswordStrength, runAdaptiveTests, inferFeatureFlags, testCatalog };