@antelopejs/dms-frontend 0.10.4 → 0.10.5
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/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createHash,
|
|
5
5
|
randomBytes,
|
|
6
6
|
} from "node:crypto";
|
|
7
|
+
import { isBoolean, isNumber, isRecord, isString } from "../type-guards.mjs";
|
|
7
8
|
|
|
8
9
|
export const SESSION_COOKIE = "dms_session";
|
|
9
10
|
const REQUEST_SESSION = Symbol("dms-request-session");
|
|
@@ -66,12 +67,17 @@ function key() {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
70
|
+
* Seals `value` for the cookie `name` alone: the name is the AES-GCM
|
|
71
|
+
* additional authenticated data, so the value opens under no other cookie.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name The cookie's name in the jar
|
|
69
74
|
* @param {unknown} value A session, or the value of a module's sealed cookie
|
|
70
75
|
* @returns {string}
|
|
71
76
|
*/
|
|
72
|
-
function encode(value) {
|
|
77
|
+
function encode(name, value) {
|
|
73
78
|
const iv = randomBytes(12);
|
|
74
79
|
const cipher = createCipheriv("aes-256-gcm", key(), iv);
|
|
80
|
+
cipher.setAAD(Buffer.from(name));
|
|
75
81
|
const encrypted = Buffer.concat([
|
|
76
82
|
cipher.update(JSON.stringify(value)),
|
|
77
83
|
cipher.final(),
|
|
@@ -82,11 +88,12 @@ function encode(value) {
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
91
|
+
* @param {string} name The cookie's name in the jar
|
|
85
92
|
* @param {string} value The cookie's value
|
|
86
|
-
* @returns {
|
|
87
|
-
* when it does not open
|
|
93
|
+
* @returns {unknown} What was sealed for `name`, as the JSON it was written
|
|
94
|
+
* from, `undefined` when it does not open
|
|
88
95
|
*/
|
|
89
|
-
function decode(value) {
|
|
96
|
+
function decode(name, value) {
|
|
90
97
|
try {
|
|
91
98
|
const payload = Buffer.from(value, "base64url");
|
|
92
99
|
const decipher = createDecipheriv(
|
|
@@ -95,6 +102,7 @@ function decode(value) {
|
|
|
95
102
|
payload.subarray(0, 12),
|
|
96
103
|
);
|
|
97
104
|
decipher.setAuthTag(payload.subarray(12, 28));
|
|
105
|
+
decipher.setAAD(Buffer.from(name));
|
|
98
106
|
return JSON.parse(
|
|
99
107
|
Buffer.concat([
|
|
100
108
|
decipher.update(payload.subarray(28)),
|
|
@@ -107,16 +115,21 @@ function decode(value) {
|
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
/**
|
|
110
|
-
* @param {
|
|
111
|
-
* @
|
|
112
|
-
* @returns {string | undefined}
|
|
118
|
+
* @param {unknown} value
|
|
119
|
+
* @returns {value is Session} Whether `value` is what `writeSession` seals
|
|
113
120
|
*/
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
function isSession(value) {
|
|
122
|
+
if (!isRecord(value) || !isBoolean(value.persistent)) return false;
|
|
123
|
+
const { user } = value;
|
|
124
|
+
return (
|
|
125
|
+
["accessToken", "refreshToken", "accountId", "activeTenantId"].every(
|
|
126
|
+
(field) => !Object.hasOwn(value, field) || isString(value[field]),
|
|
127
|
+
) &&
|
|
128
|
+
(!Object.hasOwn(value, "updatedAt") || isNumber(value.updatedAt)) &&
|
|
129
|
+
(!Object.hasOwn(value, "user") ||
|
|
130
|
+
(isRecord(user) &&
|
|
131
|
+
(!Object.hasOwn(user, "language") || isString(user.language))))
|
|
132
|
+
);
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
/**
|
|
@@ -158,6 +171,21 @@ function appendCookie(response, name, value, cookieAttributes) {
|
|
|
158
171
|
);
|
|
159
172
|
}
|
|
160
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @param {import("node:http").ServerResponse} response
|
|
176
|
+
* @param {string} name
|
|
177
|
+
* @param {unknown} value
|
|
178
|
+
* @param {string} cookieAttributes
|
|
179
|
+
*/
|
|
180
|
+
function appendSealedCookie(response, name, value, cookieAttributes) {
|
|
181
|
+
appendCookie(
|
|
182
|
+
response,
|
|
183
|
+
name,
|
|
184
|
+
encode(scopedCookieName(name), value),
|
|
185
|
+
cookieAttributes,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
161
189
|
/**
|
|
162
190
|
* @param {SessionRequest} request
|
|
163
191
|
* @returns {Session | undefined} The session set for the request, else the one
|
|
@@ -165,9 +193,8 @@ function appendCookie(response, name, value, cookieAttributes) {
|
|
|
165
193
|
*/
|
|
166
194
|
export function readSession(request) {
|
|
167
195
|
if (REQUEST_SESSION in request) return request[REQUEST_SESSION];
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
return value ? decode(value) : undefined;
|
|
196
|
+
const sealed = readSealedCookie(request, SESSION_COOKIE);
|
|
197
|
+
return isSession(sealed) ? sealed : undefined;
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
/**
|
|
@@ -183,10 +210,10 @@ export function setRequestSession(request, session) {
|
|
|
183
210
|
* @param {Session} session
|
|
184
211
|
*/
|
|
185
212
|
export function writeSession(response, session) {
|
|
186
|
-
|
|
213
|
+
appendSealedCookie(
|
|
187
214
|
response,
|
|
188
215
|
SESSION_COOKIE,
|
|
189
|
-
|
|
216
|
+
session,
|
|
190
217
|
attributes(lifetime(session)),
|
|
191
218
|
);
|
|
192
219
|
}
|
|
@@ -203,8 +230,12 @@ export function clearSession(response) {
|
|
|
203
230
|
* `undefined` when there is none or it does not open
|
|
204
231
|
*/
|
|
205
232
|
export function readSealedCookie(request, name) {
|
|
206
|
-
const
|
|
207
|
-
|
|
233
|
+
const scoped = scopedCookieName(name);
|
|
234
|
+
const cookies = request.headers.cookie?.split(";") ?? [];
|
|
235
|
+
const value = cookies
|
|
236
|
+
.map((part) => part.trim().split("="))
|
|
237
|
+
.find(([key]) => key === scoped)?.[1];
|
|
238
|
+
return value ? decode(scoped, value) : undefined;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
241
|
/**
|
|
@@ -215,7 +246,7 @@ export function readSealedCookie(request, name) {
|
|
|
215
246
|
* @param {string} [path]
|
|
216
247
|
*/
|
|
217
248
|
export function writeSealedCookie(response, name, value, maxAge, path = "/") {
|
|
218
|
-
|
|
249
|
+
appendSealedCookie(response, name, value, attributes(maxAge, path));
|
|
219
250
|
}
|
|
220
251
|
|
|
221
252
|
/**
|