@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.
@@ -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 { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
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
- if (isSqlClient(source)) {
23
- return source;
31
+ const client = isSqlClient(source) ? source : source();
32
+ if (!hasActiveDatabaseConnection()) {
33
+ return client;
24
34
  }
25
- return source();
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 rows = await sql.unsafe(`SELECT s.user_id, s.expires_at, u.*
63
- FROM sessions s
64
- INNER JOIN users u ON u.id = s.user_id
65
- WHERE s.id = ${sqlPlaceholder(1)} AND s.expires_at > ${sqlNow()}`, [sessionId]);
66
- const row = rows[0];
67
- if (!row)
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
- return mapSessionUserRow(row);
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 this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address, last_active_at)
125
- VALUES (${sqlPlaceholder(1)}, ${sqlPlaceholder(2)}, ${sqlPlaceholder(3)}, ${sqlPlaceholder(4)}, ${sqlPlaceholder(5)}, ${sqlNow()})`, [id, user.id, sqlTimestamp(expires), meta.userAgent ?? null, meta.ipAddress ?? null]);
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 this.sql().unsafe(`DELETE FROM sessions WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
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 this.sql().unsafe(`DELETE FROM sessions WHERE user_id = ${sqlPlaceholder(1)} AND id <> ${sqlPlaceholder(2)}`, [userId, keepSessionId]);
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 this.sql().unsafe(`UPDATE sessions SET last_active_at = ${sqlNow()} WHERE id = ${sqlPlaceholder(1)}`, [sessionId]);
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);