mbkauthe 4.9.0 → 5.0.0
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/docs/api.md +29 -178
- package/docs/db.md +1 -1
- package/docs/db.sql +305 -253
- 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 +1 -1
- package/public/main.css +1 -1
- package/test.spec.js +515 -48
- package/views/pages/dbLogs.handlebars +618 -420
package/lib/config/cookies.js
CHANGED
|
@@ -14,6 +14,60 @@ const getEncryptionKey = () => {
|
|
|
14
14
|
return crypto.createHash('sha256').update(COOKIE_ENCRYPTION_KEY).digest();
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const getSigningKey = () => {
|
|
18
|
+
return crypto.createHash('sha256').update(`${COOKIE_ENCRYPTION_KEY}:cookie-signing`).digest();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const encodePayload = (data) => {
|
|
22
|
+
return Buffer.from(JSON.stringify(data), 'utf8').toString('base64url');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const decodePayload = (encoded) => {
|
|
26
|
+
return JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const signCookiePayload = (encodedPayload) => {
|
|
30
|
+
return crypto.createHmac('sha256', getSigningKey()).update(encodedPayload).digest('hex');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const verifyCookieSignature = (encodedPayload, signature) => {
|
|
34
|
+
if (!encodedPayload || !signature || typeof encodedPayload !== 'string' || typeof signature !== 'string') {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const expected = signCookiePayload(encodedPayload);
|
|
39
|
+
const expectedBuffer = Buffer.from(expected, 'hex');
|
|
40
|
+
const actualBuffer = Buffer.from(signature, 'hex');
|
|
41
|
+
|
|
42
|
+
return expectedBuffer.length === actualBuffer.length && crypto.timingSafeEqual(expectedBuffer, actualBuffer);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const createSignedCookiePayload = (data) => {
|
|
46
|
+
try {
|
|
47
|
+
const payload = encodePayload(data);
|
|
48
|
+
return {
|
|
49
|
+
payload,
|
|
50
|
+
signature: signCookiePayload(payload)
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`[mbkauthe] Cookie signing error:`, error);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const parseSignedCookiePayload = (signedPayload) => {
|
|
59
|
+
try {
|
|
60
|
+
if (!signedPayload || !verifyCookieSignature(signedPayload.payload, signedPayload.signature)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return decodePayload(signedPayload.payload);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`[mbkauthe] Cookie signature verification error:`, error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
17
71
|
// Encrypt and sign cookie payload
|
|
18
72
|
const encryptCookiePayload = (data) => {
|
|
19
73
|
try {
|
|
@@ -160,33 +214,46 @@ export { getCookieOptions, getClearCookieOptions };
|
|
|
160
214
|
const parseAccountList = (raw, req) => {
|
|
161
215
|
if (!raw) return [];
|
|
162
216
|
try {
|
|
163
|
-
// First, decrypt the cookie payload
|
|
164
217
|
const parsed = JSON.parse(raw);
|
|
165
|
-
|
|
218
|
+
let data = parseSignedCookiePayload(parsed);
|
|
219
|
+
let isLegacyEncryptedCookie = false;
|
|
220
|
+
|
|
221
|
+
// Backward compatibility for previously encrypted account-list cookies.
|
|
222
|
+
if (!data && parsed.iv && parsed.authTag && parsed.data) {
|
|
223
|
+
data = decryptCookiePayload(parsed);
|
|
224
|
+
isLegacyEncryptedCookie = true;
|
|
225
|
+
}
|
|
166
226
|
|
|
167
|
-
if (!
|
|
227
|
+
if (!data || !data.accounts || !data.fingerprint) {
|
|
168
228
|
return [];
|
|
169
229
|
}
|
|
170
230
|
|
|
171
231
|
// Verify fingerprint matches current request
|
|
172
232
|
const currentFingerprint = generateFingerprint(req);
|
|
173
|
-
if (
|
|
233
|
+
if (data.fingerprint !== currentFingerprint) {
|
|
174
234
|
console.warn(`[mbkauthe] Cookie fingerprint mismatch - possible cookie theft attempt`);
|
|
175
235
|
return [];
|
|
176
236
|
}
|
|
177
237
|
|
|
178
|
-
const accounts =
|
|
238
|
+
const accounts = data.accounts;
|
|
179
239
|
if (!Array.isArray(accounts)) return [];
|
|
180
240
|
|
|
181
241
|
// Accept only minimal safe fields
|
|
182
242
|
return accounts
|
|
183
243
|
.filter(item => item && typeof item === 'object')
|
|
184
|
-
.map(item =>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
244
|
+
.map(item => {
|
|
245
|
+
const rawSessionId = typeof item.sessionId === 'string' ? item.sessionId : null;
|
|
246
|
+
const sessionId = isLegacyEncryptedCookie
|
|
247
|
+
? rawSessionId
|
|
248
|
+
: decryptSessionId(rawSessionId);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
sessionId,
|
|
252
|
+
username: typeof item.username === 'string' ? item.username : null,
|
|
253
|
+
fullName: typeof item.fullName === 'string' ? item.fullName : null,
|
|
254
|
+
image: typeof item.image === 'string' ? item.image : null
|
|
255
|
+
};
|
|
256
|
+
})
|
|
190
257
|
.filter(item => item.sessionId && item.username)
|
|
191
258
|
.slice(0, MAX_REMEMBERED_ACCOUNTS);
|
|
192
259
|
} catch (error) {
|
|
@@ -200,7 +267,7 @@ const writeAccountList = (res, list, req) => {
|
|
|
200
267
|
|
|
201
268
|
// Clean and limit fields to safe values (limit image URL length)
|
|
202
269
|
const cleaned = sanitized.map(item => ({
|
|
203
|
-
sessionId: item && item.sessionId ? item.sessionId : null,
|
|
270
|
+
sessionId: item && item.sessionId ? encryptSessionId(item.sessionId) : null,
|
|
204
271
|
username: item && item.username ? item.username : null,
|
|
205
272
|
fullName: item && item.fullName ? item.fullName : null,
|
|
206
273
|
image: (item && typeof item.image === 'string' && item.image.length <= 2048) ? item.image : null
|
|
@@ -212,14 +279,13 @@ const writeAccountList = (res, list, req) => {
|
|
|
212
279
|
fingerprint: generateFingerprint(req)
|
|
213
280
|
};
|
|
214
281
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
console.error(`[mbkauthe] Failed to encrypt account list cookie`);
|
|
282
|
+
const signed = createSignedCookiePayload(payload);
|
|
283
|
+
if (!signed) {
|
|
284
|
+
console.error(`[mbkauthe] Failed to sign account list cookie`);
|
|
219
285
|
return;
|
|
220
286
|
}
|
|
221
287
|
|
|
222
|
-
res.cookie(ACCOUNT_LIST_COOKIE, JSON.stringify(
|
|
288
|
+
res.cookie(ACCOUNT_LIST_COOKIE, JSON.stringify(signed), cachedCookieOptions);
|
|
223
289
|
};
|
|
224
290
|
|
|
225
291
|
export const readAccountListFromCookie = (req) => {
|
|
@@ -243,4 +309,4 @@ export const removeAccountFromCookie = (req, res, sessionId) => {
|
|
|
243
309
|
|
|
244
310
|
export const clearAccountListCookie = (res) => {
|
|
245
311
|
res.clearCookie(ACCOUNT_LIST_COOKIE, cachedClearCookieOptions);
|
|
246
|
-
};
|
|
312
|
+
};
|
package/lib/config/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
import { createRequire } from "module";
|
|
3
|
+
import { createLogger } from "../utils/logger.js";
|
|
3
4
|
|
|
4
5
|
dotenv.config();
|
|
6
|
+
const logConfig = createLogger("config");
|
|
5
7
|
|
|
6
8
|
// Comprehensive validation function
|
|
7
9
|
function validateConfiguration() {
|
|
@@ -215,7 +217,7 @@ function validateConfiguration() {
|
|
|
215
217
|
configParts.push(`defaults: ${usedDefaults.length} keys`);
|
|
216
218
|
}
|
|
217
219
|
const configSummary = configParts.length > 0 ? ` (${configParts.join(', ')})` : '';
|
|
218
|
-
|
|
220
|
+
logConfig(`Configuration loaded${configSummary}`);
|
|
219
221
|
return mbkautheVar;
|
|
220
222
|
}
|
|
221
223
|
|
package/lib/createTable.js
CHANGED
|
@@ -2,33 +2,120 @@
|
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { performance } from "perf_hooks";
|
|
5
6
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = path.dirname(__filename);
|
|
8
9
|
|
|
9
10
|
async function main() {
|
|
11
|
+
const startTime = performance.now();
|
|
12
|
+
|
|
13
|
+
console.log("[mbkauthe] Starting schema creation...");
|
|
14
|
+
|
|
10
15
|
const schemaPath = path.resolve(__dirname, "../docs/db.sql");
|
|
16
|
+
|
|
17
|
+
console.log(`[mbkauthe] Schema file: ${schemaPath}`);
|
|
18
|
+
|
|
11
19
|
const schemaSql = await readFile(schemaPath, "utf8");
|
|
12
20
|
|
|
21
|
+
console.log(
|
|
22
|
+
`[mbkauthe] Schema loaded (${Buffer.byteLength(schemaSql, "utf8")} bytes)`
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const statementCount = schemaSql
|
|
26
|
+
.split(";")
|
|
27
|
+
.map(s => s.trim())
|
|
28
|
+
.filter(Boolean).length;
|
|
29
|
+
|
|
30
|
+
console.log(
|
|
31
|
+
`[mbkauthe] Detected approximately ${statementCount} SQL statements`
|
|
32
|
+
);
|
|
33
|
+
|
|
13
34
|
try {
|
|
35
|
+
console.log("[mbkauthe] Testing database connection...");
|
|
36
|
+
|
|
37
|
+
const ping = await dblogin.query("SELECT version()");
|
|
38
|
+
|
|
39
|
+
console.log(
|
|
40
|
+
`[mbkauthe] Connected to PostgreSQL`
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
console.log(
|
|
44
|
+
`[mbkauthe] PostgreSQL version: ${ping.rows[0].version}`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log("[mbkauthe] Applying schema...");
|
|
48
|
+
|
|
49
|
+
const queryStart = performance.now();
|
|
50
|
+
|
|
14
51
|
const res = await dblogin.query(schemaSql);
|
|
15
|
-
|
|
52
|
+
|
|
53
|
+
const queryDuration = (
|
|
54
|
+
performance.now() - queryStart
|
|
55
|
+
).toFixed(2);
|
|
56
|
+
|
|
57
|
+
console.log(
|
|
58
|
+
`[mbkauthe] Schema applied successfully in ${queryDuration} ms`
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
console.log(
|
|
62
|
+
`[mbkauthe] Command: ${res.command ?? "MULTI"}`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
console.log(
|
|
66
|
+
`[mbkauthe] Row count: ${res.rowCount ?? 0}`
|
|
67
|
+
);
|
|
68
|
+
|
|
16
69
|
if (res?.rows?.length) {
|
|
17
|
-
console.log(
|
|
18
|
-
|
|
19
|
-
|
|
70
|
+
console.log(
|
|
71
|
+
`[mbkauthe] Returned rows: ${res.rows.length}`
|
|
72
|
+
);
|
|
20
73
|
}
|
|
74
|
+
|
|
21
75
|
} catch (err) {
|
|
22
76
|
const IGNORE_CODES = ["42710", "42P07"];
|
|
23
|
-
|
|
24
|
-
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
err &&
|
|
80
|
+
typeof err.code === "string" &&
|
|
81
|
+
IGNORE_CODES.includes(err.code)
|
|
82
|
+
) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`[mbkauthe] Schema object already exists (ignored)`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
console.warn(`Code: ${err.code}`);
|
|
88
|
+
console.warn(`Message: ${err.message}`);
|
|
25
89
|
} else {
|
|
26
|
-
console.error(
|
|
90
|
+
console.error(
|
|
91
|
+
`[mbkauthe] Failed to apply schema`
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
console.error("Code:", err.code);
|
|
95
|
+
console.error("Severity:", err.severity);
|
|
96
|
+
console.error("Position:", err.position);
|
|
97
|
+
console.error("Table:", err.table);
|
|
98
|
+
console.error("Column:", err.column);
|
|
99
|
+
console.error("Constraint:", err.constraint);
|
|
100
|
+
console.error("Detail:", err.detail);
|
|
101
|
+
console.error("Hint:", err.hint);
|
|
102
|
+
console.error("Message:", err.message);
|
|
103
|
+
|
|
27
104
|
process.exitCode = 1;
|
|
28
105
|
}
|
|
29
106
|
} finally {
|
|
107
|
+
console.log("[mbkauthe] Closing database connection...");
|
|
108
|
+
|
|
30
109
|
await dblogin.end();
|
|
110
|
+
|
|
111
|
+
const totalDuration = (
|
|
112
|
+
performance.now() - startTime
|
|
113
|
+
).toFixed(2);
|
|
114
|
+
|
|
115
|
+
console.log(
|
|
116
|
+
`[mbkauthe] Finished in ${totalDuration} ms`
|
|
117
|
+
);
|
|
31
118
|
}
|
|
32
119
|
}
|
|
33
120
|
|
|
34
|
-
main();
|
|
121
|
+
main();
|
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());
|