mbkauthe 4.9.0 → 5.0.1
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/README.md +98 -112
- package/docs/README.md +33 -0
- package/docs/STYLE.md +40 -0
- package/docs/diagrams/auth-processes.mmd +120 -0
- package/docs/diagrams/c.md +122 -0
- package/docs/{env.md → guides/configuration.md} +2 -1
- package/docs/{db.md → guides/database.md} +4 -2
- package/docs/images/auth-process.svg +102 -1
- package/docs/images/auth-processes.svg +1 -0
- package/docs/reference/api/authentication.md +105 -0
- package/docs/{api.md → reference/api/endpoints.md} +31 -861
- package/docs/reference/api/examples.md +251 -0
- package/docs/reference/api/middleware.md +239 -0
- package/docs/reference/api/operations.md +52 -0
- package/docs/reference/api.md +19 -0
- package/docs/{error-messages.md → reference/error-codes.md} +2 -0
- package/docs/schema/db.sql +328 -0
- package/index.js +5 -3
- package/lib/config/cookies.js +84 -18
- package/lib/config/index.js +3 -1
- package/lib/config/tokenScopes.js +1 -1
- package/lib/createTable.js +95 -8
- package/lib/db/AuthRepository.js +57 -16
- package/lib/db/BaseRepository.js +9 -1
- package/lib/db/dialects/postgres.js +1 -1
- package/lib/main.js +5 -5
- package/lib/middleware/auth.js +201 -218
- package/lib/middleware/index.js +13 -14
- package/lib/middleware/scopeValidator.js +8 -3
- package/lib/pool.js +5 -6
- package/lib/routes/auth.js +42 -47
- package/lib/routes/dbLogs.js +247 -29
- package/lib/routes/misc.js +6 -4
- package/lib/routes/oauth.js +19 -23
- package/lib/utils/dbQueryLogger.js +485 -80
- package/lib/utils/errors.js +1 -1
- package/lib/utils/logger.js +12 -0
- package/lib/utils/timingSafeToken.js +1 -1
- package/package.json +4 -3
- package/public/main.css +1 -1
- package/test.spec.js +515 -48
- package/views/pages/dbLogs.handlebars +618 -420
- package/docs/auth-processes.mmd +0 -71
- package/docs/db.sql +0 -276
- /package/docs/{auth-flows.mmd → diagrams/auth-flows.mmd} +0 -0
package/lib/db/AuthRepository.js
CHANGED
|
@@ -14,6 +14,28 @@ const OAUTH_PROVIDERS = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export class AuthRepository extends BaseRepository {
|
|
17
|
+
buildSessionUserSelect({ includeProfile = false, includeTwoFA = false } = {}) {
|
|
18
|
+
const fields = [
|
|
19
|
+
`s.id as sid`,
|
|
20
|
+
`s.expires_at`,
|
|
21
|
+
`u.id as uid`,
|
|
22
|
+
`u."UserName"`,
|
|
23
|
+
`u."Active"`,
|
|
24
|
+
`u."Role"`,
|
|
25
|
+
`u."AllowedApps"`
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
if (includeProfile) {
|
|
29
|
+
fields.push(`u."FullName"`, `u."Image"`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (includeTwoFA) {
|
|
33
|
+
fields.push(`tfa."TwoFAStatus"`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return fields.join(", ");
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
resolveOAuthProvider(provider) {
|
|
18
40
|
const key = String(provider || "").toLowerCase();
|
|
19
41
|
const config = OAUTH_PROVIDERS[key];
|
|
@@ -24,7 +46,7 @@ export class AuthRepository extends BaseRepository {
|
|
|
24
46
|
}
|
|
25
47
|
|
|
26
48
|
async fetchActiveSession(sessionId) {
|
|
27
|
-
const query = `SELECT
|
|
49
|
+
const query = `SELECT ${this.buildSessionUserSelect({ includeProfile: true })}
|
|
28
50
|
FROM "Sessions" s
|
|
29
51
|
JOIN "Users" u ON s."UserName" = u."UserName"
|
|
30
52
|
WHERE s.id = $1 LIMIT 1`;
|
|
@@ -42,6 +64,17 @@ export class AuthRepository extends BaseRepository {
|
|
|
42
64
|
return this.executeRaw({ name: queryName, text: query, values: [sessionId] });
|
|
43
65
|
}
|
|
44
66
|
|
|
67
|
+
async getSessionsWithUsersByIds(sessionIds, queryName = "multi-session-fetch-many") {
|
|
68
|
+
if (!Array.isArray(sessionIds) || sessionIds.length === 0) return [];
|
|
69
|
+
|
|
70
|
+
const query = `SELECT ${this.buildSessionUserSelect({ includeProfile: true })}
|
|
71
|
+
FROM "Sessions" s
|
|
72
|
+
JOIN "Users" u ON s."UserName" = u."UserName"
|
|
73
|
+
WHERE s.id = ANY($1)`;
|
|
74
|
+
const result = await this.executeRaw({ name: queryName, text: query, values: [sessionIds] });
|
|
75
|
+
return result.rows || [];
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
async touchTrustedDevice(deviceTokenHash, username) {
|
|
46
79
|
const query = `
|
|
47
80
|
UPDATE "TrustedDevices" td
|
|
@@ -164,7 +197,7 @@ export class AuthRepository extends BaseRepository {
|
|
|
164
197
|
async getUserWithTwoFA(username, queryName = "login-get-user") {
|
|
165
198
|
const query = `
|
|
166
199
|
SELECT u.id, u."UserName", u."PasswordEnc", u."Active", u."Role", u."AllowedApps",
|
|
167
|
-
tfa."TwoFAStatus"
|
|
200
|
+
tfa."TwoFAStatus", u."FullName", u."Image"
|
|
168
201
|
FROM "Users" u
|
|
169
202
|
LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
|
|
170
203
|
WHERE u."UserName" = $1
|
|
@@ -189,7 +222,11 @@ export class AuthRepository extends BaseRepository {
|
|
|
189
222
|
|
|
190
223
|
async getOAuthUserByProviderId(provider, providerId) {
|
|
191
224
|
const { table, idColumn, queryName } = this.resolveOAuthProvider(provider);
|
|
192
|
-
const query = `SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps", u."id"
|
|
225
|
+
const query = `SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps", u."id", tfa."TwoFAStatus"
|
|
226
|
+
FROM ${table} ug
|
|
227
|
+
JOIN "Users" u ON ug.user_name = u."UserName"
|
|
228
|
+
LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
|
|
229
|
+
WHERE ug.${idColumn} = $1`;
|
|
193
230
|
const result = await this.executeRaw({ name: queryName, text: query, values: [providerId] });
|
|
194
231
|
return result.rows?.[0] || null;
|
|
195
232
|
}
|
|
@@ -207,17 +244,26 @@ export class AuthRepository extends BaseRepository {
|
|
|
207
244
|
return result.rows?.[0] || null;
|
|
208
245
|
}
|
|
209
246
|
|
|
210
|
-
async updateApiTokenLastUsed(tokenId, queryName = null) {
|
|
211
|
-
const query = `
|
|
247
|
+
async updateApiTokenLastUsed(tokenId, queryName = null, minIntervalMinutes = 15) {
|
|
248
|
+
const query = `
|
|
249
|
+
UPDATE "ApiTokens"
|
|
250
|
+
SET "LastUsed" = NOW()
|
|
251
|
+
WHERE id = $1
|
|
252
|
+
AND (
|
|
253
|
+
"LastUsed" IS NULL
|
|
254
|
+
OR "LastUsed" < NOW() - ($2::int * INTERVAL '1 minute')
|
|
255
|
+
)
|
|
256
|
+
`;
|
|
257
|
+
const values = [tokenId, minIntervalMinutes];
|
|
212
258
|
if (queryName) {
|
|
213
|
-
return this.executeRaw({ name: queryName, text: query, values
|
|
259
|
+
return this.executeRaw({ name: queryName, text: query, values });
|
|
214
260
|
}
|
|
215
|
-
return this.executeRaw({ text: query, values
|
|
261
|
+
return this.executeRaw({ text: query, values });
|
|
216
262
|
}
|
|
217
263
|
|
|
218
264
|
async getSessionAuthData(sessionId, queryName = "validate-app-session") {
|
|
219
265
|
const query = `
|
|
220
|
-
SELECT s.expires_at, u."Active", u."Role"
|
|
266
|
+
SELECT s.expires_at, u."Active", u."Role", u."AllowedApps", u."UserName"
|
|
221
267
|
FROM "Sessions" s
|
|
222
268
|
JOIN "Users" u ON s."UserName" = u."UserName"
|
|
223
269
|
WHERE s.id = $1
|
|
@@ -229,7 +275,7 @@ export class AuthRepository extends BaseRepository {
|
|
|
229
275
|
}
|
|
230
276
|
|
|
231
277
|
async getSessionWithUserById(sessionId, queryName = "restore-user-session") {
|
|
232
|
-
const query = `SELECT
|
|
278
|
+
const query = `SELECT ${this.buildSessionUserSelect({ includeProfile: true })}
|
|
233
279
|
FROM "Sessions" s
|
|
234
280
|
JOIN "Users" u ON s."UserName" = u."UserName"
|
|
235
281
|
WHERE s.id = $1 LIMIT 1`;
|
|
@@ -238,12 +284,7 @@ export class AuthRepository extends BaseRepository {
|
|
|
238
284
|
}
|
|
239
285
|
|
|
240
286
|
async getSessionWithUserForReload(sessionId, queryName = "reload-session-user") {
|
|
241
|
-
|
|
242
|
-
FROM "Sessions" s
|
|
243
|
-
JOIN "Users" u ON s."UserName" = u."UserName"
|
|
244
|
-
WHERE s.id = $1 LIMIT 1`;
|
|
245
|
-
const result = await this.executeRaw({ name: queryName, text: query, values: [sessionId] });
|
|
246
|
-
return result.rows?.[0] || null;
|
|
287
|
+
return this.getSessionWithUserById(sessionId, queryName);
|
|
247
288
|
}
|
|
248
289
|
|
|
249
290
|
async getSessionValidationRow(sessionId, queryName = "check-session-validity-by-id") {
|
|
@@ -292,4 +333,4 @@ export class AuthRepository extends BaseRepository {
|
|
|
292
333
|
const query = `DELETE FROM "session" WHERE expire > NOW()`;
|
|
293
334
|
return this.executeRaw({ name: queryName, text: query, values: [] });
|
|
294
335
|
}
|
|
295
|
-
}
|
|
336
|
+
}
|
package/lib/db/BaseRepository.js
CHANGED
|
@@ -182,4 +182,12 @@ export class BaseRepository {
|
|
|
182
182
|
values: []
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
|
|
186
|
+
async advisoryTransactionLock(lockKey, queryName = "advisory-transaction-lock") {
|
|
187
|
+
return this.executeRaw({
|
|
188
|
+
name: queryName,
|
|
189
|
+
text: "SELECT pg_advisory_xact_lock(hashtext($1))",
|
|
190
|
+
values: [String(lockKey ?? "")]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
package/lib/main.js
CHANGED
|
@@ -41,17 +41,17 @@ router.use(cookieParser());
|
|
|
41
41
|
// CORS and security headers
|
|
42
42
|
router.use(corsMiddleware);
|
|
43
43
|
|
|
44
|
+
// Attach request context as early as possible so session-store queries are tied to the request.
|
|
45
|
+
if (process.env.env === 'dev') {
|
|
46
|
+
router.use(requestContextMiddleware);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
// Session configuration
|
|
45
50
|
router.use(session(sessionConfig));
|
|
46
51
|
|
|
47
52
|
// Session restoration
|
|
48
53
|
router.use(sessionRestorationMiddleware);
|
|
49
54
|
|
|
50
|
-
// Attach request context for DB query logging (dev only)
|
|
51
|
-
if (process.env.env === 'dev') {
|
|
52
|
-
router.use(requestContextMiddleware);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
55
|
// Initialize passport
|
|
56
56
|
router.use(passport.initialize());
|
|
57
57
|
router.use(passport.session());
|