@getstrata/bootstrap 1.0.9 → 1.1.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/CHANGELOG.md +8 -0
- package/dist/bootstrap/discoverListeners.d.ts +2 -2
- package/dist/bootstrap/secretsGuard.d.ts +13 -1
- package/dist/bootstrap/web/session.d.ts +1 -0
- package/dist/entries/buildModuleRoutes.js +34 -38
- package/dist/entries/buildWebModuleRoutes.js +34 -38
- package/dist/entries/context.js +27 -11
- package/dist/entries/createSpaRoutes.js +10 -3
- package/dist/entries/createWebRoutes.js +43 -42
- package/dist/entries/dependencies.js +27 -11
- package/dist/entries/discoverListeners.js +54 -0
- package/dist/entries/health.js +3 -2
- package/dist/entries/httpKernel.js +28 -37
- package/dist/entries/metricsRoutes.js +2 -1
- package/dist/entries/providers.js +29 -13
- package/dist/entries/schedule.js +2 -2
- package/dist/entries/secretsGuard.js +100 -5
- package/dist/entries/web/routing.js +28 -37
- package/dist/entries/web/server.js +9 -3
- package/dist/entries/web/session.js +68 -18
- package/dist/index.js +225 -87
- package/package.json +8 -3
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
// ../../src/bootstrap/web/session.ts
|
|
3
3
|
import { createHmac, randomBytes } from "crypto";
|
|
4
4
|
import { AuthManager } from "@getstrata/core/auth/guard";
|
|
5
|
+
import { isSessionInvalidated } from "@getstrata/core/auth/sessionCookie";
|
|
5
6
|
import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getActiveDatabaseConnection,
|
|
9
|
+
hasActiveDatabaseConnection
|
|
10
|
+
} from "@getstrata/core/database/connectionContext";
|
|
11
|
+
import {
|
|
12
|
+
getDefaultDatabasePool,
|
|
13
|
+
getDefaultDatabaseQuery
|
|
14
|
+
} from "@getstrata/core/database/defaultConnection";
|
|
7
15
|
import { currentSqlDialect, sqlTimestamp } from "@getstrata/core/database/dialect";
|
|
8
16
|
import { readRequestCookie } from "@getstrata/core/http/cookies";
|
|
9
17
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
10
18
|
import { isProductionEnv } from "@getstrata/core/runtime/appEnv";
|
|
11
19
|
import { timingSafeCompareString } from "@getstrata/core/security/timingSafeCompare";
|
|
20
|
+
import { runWithMigrationBypassForIdentifier } from "@getstrata/core/tenant/databaseTenantContext";
|
|
12
21
|
function sqlPlaceholder(index) {
|
|
13
22
|
return currentSqlDialect().placeholder(index);
|
|
14
23
|
}
|
|
@@ -19,10 +28,18 @@ function isSqlClient(value) {
|
|
|
19
28
|
return typeof value.unsafe === "function";
|
|
20
29
|
}
|
|
21
30
|
function resolveSql(source) {
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
const client = isSqlClient(source) ? source : source();
|
|
32
|
+
if (!hasActiveDatabaseConnection()) {
|
|
33
|
+
return client;
|
|
24
34
|
}
|
|
25
|
-
|
|
35
|
+
try {
|
|
36
|
+
if (client === getDefaultDatabasePool() || client === getDefaultDatabaseQuery()) {
|
|
37
|
+
return getActiveDatabaseConnection(client);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
return client;
|
|
41
|
+
}
|
|
42
|
+
return client;
|
|
26
43
|
}
|
|
27
44
|
function defaultSessionSql() {
|
|
28
45
|
const bound = getBoundDatabaseConnection();
|
|
@@ -59,14 +76,32 @@ function mapSessionUserRow(row) {
|
|
|
59
76
|
};
|
|
60
77
|
}
|
|
61
78
|
async function defaultLoadSessionUser(sql, sessionId) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
WHERE
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
const sessionRows = await runWithMigrationBypassForIdentifier(sessionId, async () => {
|
|
80
|
+
return await sql.unsafe(`SELECT user_id, expires_at, created_at AS session_created_at
|
|
81
|
+
FROM sessions
|
|
82
|
+
WHERE id = ${sqlPlaceholder(1)} AND expires_at > ${sqlNow()}`, [sessionId]);
|
|
83
|
+
});
|
|
84
|
+
const session = sessionRows[0];
|
|
85
|
+
if (!session)
|
|
86
|
+
return null;
|
|
87
|
+
const userId = Number(session.user_id);
|
|
88
|
+
if (!Number.isInteger(userId) || userId <= 0) {
|
|
68
89
|
return null;
|
|
69
|
-
|
|
90
|
+
}
|
|
91
|
+
return await runWithMigrationBypassForIdentifier(userId, async () => {
|
|
92
|
+
const rows = await sql.unsafe(`SELECT * FROM users WHERE id = ${sqlPlaceholder(1)}`, [
|
|
93
|
+
userId
|
|
94
|
+
]);
|
|
95
|
+
const row = rows[0];
|
|
96
|
+
if (!row)
|
|
97
|
+
return null;
|
|
98
|
+
const createdSource = session.session_created_at;
|
|
99
|
+
const createdAt = createdSource instanceof Date ? createdSource.getTime() : createdSource ? Date.parse(String(createdSource)) : Number.NaN;
|
|
100
|
+
if (!Number.isFinite(createdAt) || isSessionInvalidated(createdAt, row.session_valid_after)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return mapSessionUserRow({ ...row, user_id: userId, session_created_at: createdSource });
|
|
104
|
+
});
|
|
70
105
|
}
|
|
71
106
|
function redirectWithCookie(location, setCookie, status) {
|
|
72
107
|
return new Response(null, {
|
|
@@ -121,25 +156,40 @@ class CookieSessionStore {
|
|
|
121
156
|
async create(user, meta = {}) {
|
|
122
157
|
const id = randomBytes(32).toString("hex");
|
|
123
158
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
124
|
-
await
|
|
125
|
-
|
|
159
|
+
await runWithMigrationBypassForIdentifier(user.id, async () => {
|
|
160
|
+
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at, created_at)
|
|
161
|
+
VALUES (${sqlPlaceholder(1)}, ${sqlPlaceholder(2)}, ${sqlPlaceholder(3)}, ${sqlPlaceholder(4)}, ${sqlPlaceholder(5)}, ${sqlNow()}, ${sqlNow()})`, [id, user.id, sqlTimestamp(expires), meta.userAgent ?? null, meta.ipAddress ?? null]);
|
|
162
|
+
});
|
|
126
163
|
return id;
|
|
127
164
|
}
|
|
128
165
|
async destroy(sessionId) {
|
|
129
|
-
await
|
|
166
|
+
await runWithMigrationBypassForIdentifier(sessionId, async () => {
|
|
167
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async destroyAllSessions(userId) {
|
|
171
|
+
await runWithMigrationBypassForIdentifier(userId, async () => {
|
|
172
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)}`, [
|
|
173
|
+
userId
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
130
176
|
}
|
|
131
177
|
async destroyOtherSessions(userId, keepSessionId) {
|
|
132
|
-
await
|
|
178
|
+
await runWithMigrationBypassForIdentifier(userId, async () => {
|
|
179
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)} AND id <> ${sqlPlaceholder(2)}`, [userId, keepSessionId]);
|
|
180
|
+
});
|
|
133
181
|
}
|
|
134
182
|
async listForUser(userId) {
|
|
135
183
|
const dialect = currentSqlDialect();
|
|
136
|
-
return this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
|
|
184
|
+
return await runWithMigrationBypassForIdentifier(userId, () => this.sql().unsafe(`SELECT id, user_id, user_agent, ip_address, last_active_at, expires_at
|
|
137
185
|
FROM sessions
|
|
138
186
|
WHERE user_id = ${dialect.placeholder(1)} AND expires_at > ${dialect.nowExpression()}
|
|
139
|
-
ORDER BY last_active_at DESC${dialect.nullsLastSuffix()}, expires_at DESC`, [userId]);
|
|
187
|
+
ORDER BY last_active_at DESC${dialect.nullsLastSuffix()}, expires_at DESC`, [userId]));
|
|
140
188
|
}
|
|
141
189
|
async touch(sessionId) {
|
|
142
|
-
await
|
|
190
|
+
await runWithMigrationBypassForIdentifier(sessionId, async () => {
|
|
191
|
+
await this.sql().unsafe(`UPDATE sessions SET last_active_at = ${sqlNow()} WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
|
|
192
|
+
});
|
|
143
193
|
}
|
|
144
194
|
async read(request) {
|
|
145
195
|
const sessionId = this.sessionIdFromRequest(request);
|