@getstrata/bootstrap 0.2.49 → 0.2.51
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 +13 -0
- package/README.md +3 -1
- package/dist/bootstrap/config.d.ts +1 -1
- package/dist/bootstrap/health.d.ts +8 -2
- package/dist/bootstrap/public-api.d.ts +4 -2
- package/dist/bootstrap/web/index.d.ts +1 -1
- package/dist/bootstrap/web/session.d.ts +42 -3
- package/dist/entries/buildModuleRoutes.js +19 -4
- package/dist/entries/buildWebModuleRoutes.js +19 -4
- package/dist/entries/config.js +4 -0
- package/dist/entries/context.js +2 -0
- package/dist/entries/createRoutes.js +203 -155
- package/dist/entries/createSpaRoutes.js +1 -1
- package/dist/entries/createWebRoutes.js +19 -4
- package/dist/entries/dependencies.js +2 -0
- package/dist/entries/health.js +69 -152
- package/dist/entries/httpKernel.js +19 -4
- package/dist/entries/providers.js +2 -0
- package/dist/entries/secretsGuard.js +64 -10
- package/dist/entries/web/routing.js +19 -4
- package/dist/entries/web/session.js +128 -28
- package/dist/index.js +309 -41
- package/package.json +6 -5
|
@@ -3,18 +3,71 @@ var __jsonParse = (a) => JSON.parse(a);
|
|
|
3
3
|
|
|
4
4
|
// ../../src/bootstrap/web/session.ts
|
|
5
5
|
import { createHash, randomBytes } from "crypto";
|
|
6
|
+
import { AuthManager } from "@getstrata/core/auth/guard";
|
|
7
|
+
import { getBoundDatabaseConnection } from "@getstrata/core/database/boundConnection";
|
|
8
|
+
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
6
9
|
import { readRequestCookie } from "@getstrata/core/http/cookies";
|
|
10
|
+
function isSqlClient(value) {
|
|
11
|
+
return typeof value.unsafe === "function";
|
|
12
|
+
}
|
|
13
|
+
function resolveSql(source) {
|
|
14
|
+
if (isSqlClient(source)) {
|
|
15
|
+
return source;
|
|
16
|
+
}
|
|
17
|
+
return source();
|
|
18
|
+
}
|
|
19
|
+
function defaultSessionSql() {
|
|
20
|
+
const bound = getBoundDatabaseConnection();
|
|
21
|
+
if (bound) {
|
|
22
|
+
return bound;
|
|
23
|
+
}
|
|
24
|
+
return getDefaultDatabasePool();
|
|
25
|
+
}
|
|
26
|
+
function defaultMapSessionUser(user) {
|
|
27
|
+
return {
|
|
28
|
+
id: user.id,
|
|
29
|
+
role: user.is_admin ? "admin" : "member"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function defaultLoadSessionUser(sql, sessionId) {
|
|
33
|
+
const rows = await sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
|
|
34
|
+
COALESCE(u.is_admin, false) AS is_admin
|
|
35
|
+
FROM sessions s
|
|
36
|
+
INNER JOIN users u ON u.id = s.user_id
|
|
37
|
+
WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
|
|
38
|
+
const row = rows[0];
|
|
39
|
+
if (!row)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
id: row.user_id,
|
|
43
|
+
name: row.name,
|
|
44
|
+
email: row.email,
|
|
45
|
+
learn_subscriber: row.learn_subscriber,
|
|
46
|
+
is_admin: row.is_admin
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function redirectWithCookie(location, setCookie, status) {
|
|
50
|
+
return new Response(null, {
|
|
51
|
+
status,
|
|
52
|
+
headers: {
|
|
53
|
+
Location: location,
|
|
54
|
+
"Set-Cookie": setCookie
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
7
58
|
|
|
8
59
|
class CookieSessionStore {
|
|
9
|
-
|
|
60
|
+
sqlSource;
|
|
10
61
|
secret;
|
|
11
62
|
cookieName;
|
|
12
63
|
maxAgeSeconds;
|
|
13
|
-
|
|
14
|
-
|
|
64
|
+
loadSessionUser;
|
|
65
|
+
constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14, loadSessionUser = defaultLoadSessionUser) {
|
|
66
|
+
this.sqlSource = sqlSource;
|
|
15
67
|
this.secret = secret;
|
|
16
68
|
this.cookieName = cookieName;
|
|
17
69
|
this.maxAgeSeconds = maxAgeSeconds;
|
|
70
|
+
this.loadSessionUser = loadSessionUser;
|
|
18
71
|
}
|
|
19
72
|
cookieHeader(_user, sessionId) {
|
|
20
73
|
const payload = `${sessionId}.${this.sign(sessionId)}`;
|
|
@@ -23,16 +76,30 @@ class CookieSessionStore {
|
|
|
23
76
|
clearCookieHeader() {
|
|
24
77
|
return this.withSecureFlag(`${this.cookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
|
|
25
78
|
}
|
|
79
|
+
sessionIdFromRequest(request) {
|
|
80
|
+
const cookie = readRequestCookie(request, this.cookieName);
|
|
81
|
+
const raw = cookie ?? null;
|
|
82
|
+
if (!raw)
|
|
83
|
+
return null;
|
|
84
|
+
const [sessionId, signature] = raw.split(".");
|
|
85
|
+
if (!sessionId || !signature || signature !== this.sign(sessionId)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return sessionId;
|
|
89
|
+
}
|
|
26
90
|
withSecureFlag(header) {
|
|
27
91
|
if (true) {
|
|
28
92
|
return header;
|
|
29
93
|
}
|
|
30
94
|
return header.includes("Secure") ? header : `${header}; Secure`;
|
|
31
95
|
}
|
|
96
|
+
sql() {
|
|
97
|
+
return resolveSql(this.sqlSource);
|
|
98
|
+
}
|
|
32
99
|
async create(user) {
|
|
33
100
|
const id = randomBytes(32).toString("hex");
|
|
34
101
|
const expires = new Date(Date.now() + this.maxAgeSeconds * 1000);
|
|
35
|
-
await this.sql.unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
102
|
+
await this.sql().unsafe(`INSERT INTO sessions (id, user_id, expires_at) VALUES ($1, $2, $3)`, [
|
|
36
103
|
id,
|
|
37
104
|
user.id,
|
|
38
105
|
expires
|
|
@@ -40,37 +107,70 @@ class CookieSessionStore {
|
|
|
40
107
|
return id;
|
|
41
108
|
}
|
|
42
109
|
async destroy(sessionId) {
|
|
43
|
-
await this.sql.unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
110
|
+
await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
|
|
44
111
|
}
|
|
45
112
|
async read(request) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
if (!raw)
|
|
49
|
-
return null;
|
|
50
|
-
const [sessionId, signature] = raw.split(".");
|
|
51
|
-
if (!sessionId || !signature || signature !== this.sign(sessionId)) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const rows = await this.sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
|
|
55
|
-
COALESCE(u.is_admin, false) AS is_admin
|
|
56
|
-
FROM sessions s
|
|
57
|
-
INNER JOIN users u ON u.id = s.user_id
|
|
58
|
-
WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
|
|
59
|
-
const row = rows[0];
|
|
60
|
-
if (!row)
|
|
113
|
+
const sessionId = this.sessionIdFromRequest(request);
|
|
114
|
+
if (!sessionId)
|
|
61
115
|
return null;
|
|
62
|
-
return
|
|
63
|
-
id: row.user_id,
|
|
64
|
-
name: row.name,
|
|
65
|
-
email: row.email,
|
|
66
|
-
learn_subscriber: row.learn_subscriber,
|
|
67
|
-
is_admin: row.is_admin
|
|
68
|
-
};
|
|
116
|
+
return this.loadSessionUser(this.sql(), sessionId);
|
|
69
117
|
}
|
|
70
118
|
sign(value) {
|
|
71
119
|
return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
|
|
72
120
|
}
|
|
73
121
|
}
|
|
122
|
+
|
|
123
|
+
class CookieSessionGuard {
|
|
124
|
+
store;
|
|
125
|
+
mapUser;
|
|
126
|
+
constructor(store, mapUser = defaultMapSessionUser) {
|
|
127
|
+
this.store = store;
|
|
128
|
+
this.mapUser = mapUser;
|
|
129
|
+
}
|
|
130
|
+
async resolve(request) {
|
|
131
|
+
const user = await this.store.read(request);
|
|
132
|
+
if (!user) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return this.mapUser(user);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class CookieSessionAuthManager extends AuthManager {
|
|
140
|
+
store;
|
|
141
|
+
constructor(store, mapUser = defaultMapSessionUser) {
|
|
142
|
+
super(new CookieSessionGuard(store, mapUser));
|
|
143
|
+
this.store = store;
|
|
144
|
+
}
|
|
145
|
+
async signIn(user) {
|
|
146
|
+
const sessionId = await this.store.create(user);
|
|
147
|
+
return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
|
|
148
|
+
}
|
|
149
|
+
async signOut(request) {
|
|
150
|
+
const sessionId = this.store.sessionIdFromRequest(request);
|
|
151
|
+
if (sessionId) {
|
|
152
|
+
await this.store.destroy(sessionId);
|
|
153
|
+
}
|
|
154
|
+
return { setCookie: this.store.clearCookieHeader() };
|
|
155
|
+
}
|
|
156
|
+
async signInRedirect(user, location, status = 302) {
|
|
157
|
+
const { setCookie } = await this.signIn(user);
|
|
158
|
+
return redirectWithCookie(location, setCookie, status);
|
|
159
|
+
}
|
|
160
|
+
async signOutRedirect(request, location, status = 302) {
|
|
161
|
+
const { setCookie } = await this.signOut(request);
|
|
162
|
+
return redirectWithCookie(location, setCookie, status);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function createCookieSessionAuthManager(options = {}) {
|
|
166
|
+
const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds, options.loadSessionUser);
|
|
167
|
+
return new CookieSessionAuthManager(store, options.mapUser ?? defaultMapSessionUser);
|
|
168
|
+
}
|
|
74
169
|
export {
|
|
75
|
-
|
|
170
|
+
CookieSessionAuthManager,
|
|
171
|
+
CookieSessionGuard,
|
|
172
|
+
CookieSessionStore,
|
|
173
|
+
createCookieSessionAuthManager,
|
|
174
|
+
defaultMapSessionUser,
|
|
175
|
+
defaultSessionSql
|
|
76
176
|
};
|