mbkauthe 4.8.1 → 4.8.2
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/lib/routes/auth.js +43 -37
- package/package.json +1 -1
package/lib/routes/auth.js
CHANGED
|
@@ -188,55 +188,61 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
188
188
|
});
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
// Enforce max sessions per user (configurable via mbkautheVar.MAX_SESSIONS_PER_USER)
|
|
191
|
+
// Enforce max sessions per user (configurable via mbkautheVar.MAX_SESSIONS_PER_USER)
|
|
192
|
+
// Use a transaction and lock the Sessions table to prevent concurrent logins from exceeding the configured limit.
|
|
192
193
|
const configuredMax = parseInt(mbkautheVar.MAX_SESSIONS_PER_USER, 10);
|
|
193
194
|
const MAX_SESSIONS = Number.isInteger(configuredMax) && configuredMax > 0 ? configuredMax : 5;
|
|
194
195
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
const dbClient = await dblogin.connect();
|
|
197
|
+
let dbSessionId;
|
|
198
|
+
try {
|
|
199
|
+
await dbClient.query('BEGIN');
|
|
200
|
+
await dbClient.query('LOCK TABLE "Sessions" IN SHARE ROW EXCLUSIVE MODE');
|
|
201
|
+
|
|
202
|
+
// Clean up expired sessions + count active sessions in a single round-trip.
|
|
203
|
+
const countRes = await dbClient.query({
|
|
204
|
+
name: 'cleanup-and-count-user-sessions',
|
|
205
|
+
text: `
|
|
206
|
+
WITH deleted AS (
|
|
207
|
+
DELETE FROM "Sessions"
|
|
208
|
+
WHERE "UserName" = $1
|
|
209
|
+
AND expires_at IS NOT NULL
|
|
210
|
+
AND expires_at <= NOW()
|
|
211
|
+
)
|
|
212
|
+
SELECT COUNT(*)::int AS count
|
|
213
|
+
FROM "Sessions"
|
|
201
214
|
WHERE "UserName" = $1
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
205
|
-
SELECT COUNT(*)::int AS count
|
|
206
|
-
FROM "Sessions"
|
|
207
|
-
WHERE "UserName" = $1
|
|
208
|
-
`,
|
|
209
|
-
values: [username]
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const currentSessions = Number(countRes.rows?.[0]?.count ?? 0);
|
|
213
|
-
// If we have MAX_SESSIONS or more, delete oldest to make room for exactly 1 new session
|
|
214
|
-
if (currentSessions >= MAX_SESSIONS) {
|
|
215
|
-
const sessionsToDelete = currentSessions - MAX_SESSIONS + 1; // +1 to make room for new session
|
|
216
|
-
console.log(`[mbkauthe] User "${username}" has ${currentSessions} active sessions, exceeding max of ${MAX_SESSIONS}. Deleting ${sessionsToDelete} oldest sessions.`);
|
|
217
|
-
|
|
218
|
-
await dblogin.query({
|
|
219
|
-
name: 'prune-oldest-user-session',
|
|
220
|
-
// Expired sessions were already removed above; remaining rows are active.
|
|
221
|
-
text: `DELETE FROM "Sessions" WHERE id IN (SELECT id FROM "Sessions" WHERE "UserName" = $1 ORDER BY created_at ASC LIMIT $2)`,
|
|
222
|
-
values: [username, sessionsToDelete]
|
|
215
|
+
`,
|
|
216
|
+
values: [username]
|
|
223
217
|
});
|
|
224
|
-
}
|
|
225
218
|
|
|
226
|
-
|
|
219
|
+
const currentSessions = Number(countRes.rows?.[0]?.count ?? 0);
|
|
220
|
+
if (currentSessions >= MAX_SESSIONS) {
|
|
221
|
+
const sessionsToDelete = currentSessions - MAX_SESSIONS + 1; // +1 to make room for new session
|
|
222
|
+
console.log(`[mbkauthe] User "${username}" has ${currentSessions} active sessions, exceeding max of ${MAX_SESSIONS}. Deleting ${sessionsToDelete} oldest sessions.`);
|
|
227
223
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
await dbClient.query({
|
|
225
|
+
name: 'prune-oldest-user-session',
|
|
226
|
+
text: `DELETE FROM "Sessions" WHERE id IN (SELECT id FROM "Sessions" WHERE "UserName" = $1 ORDER BY created_at ASC LIMIT $2)`,
|
|
227
|
+
values: [username, sessionsToDelete]
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const expiresAt = new Date(Date.now() + (cachedCookieOptions.maxAge || 0));
|
|
232
|
+
const insertRes = await dbClient.query({
|
|
232
233
|
name: 'insert-app-session',
|
|
233
234
|
text: `INSERT INTO "Sessions" ("UserName", expires_at, meta) VALUES ($1, $2, $3) RETURNING id`,
|
|
234
235
|
values: [username, expiresAt, JSON.stringify({ ip: req.ip, ua: req.headers['user-agent'] || null })]
|
|
235
236
|
});
|
|
236
237
|
dbSessionId = insertRes.rows[0].id;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
|
|
239
|
+
await dbClient.query('COMMIT');
|
|
240
|
+
} catch (err) {
|
|
241
|
+
await dbClient.query('ROLLBACK').catch(() => {});
|
|
242
|
+
console.error('[mbkauthe] Error enforcing session limit or inserting app session:', err);
|
|
243
|
+
throw err;
|
|
244
|
+
} finally {
|
|
245
|
+
dbClient.release();
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
// Update last_login and fetch FullName/Image in a single query.
|