@vielzeug/archive 26.11.15 → 26.11.17
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/dist/auth/_confirmation.cjs +20 -19
- package/dist/auth/_confirmation.cjs.map +1 -1
- package/dist/auth/_confirmation.d.ts +4 -1
- package/dist/auth/_confirmation.d.ts.map +1 -1
- package/dist/auth/_confirmation.js +23 -22
- package/dist/auth/_confirmation.js.map +1 -1
- package/dist/auth/store.cjs +5 -4
- package/dist/auth/store.cjs.map +1 -1
- package/dist/auth/store.d.ts +2 -0
- package/dist/auth/store.d.ts.map +1 -1
- package/dist/auth/store.js +7 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/auth/worker.cjs +1 -1
- package/dist/auth/worker.cjs.map +1 -1
- package/dist/auth/worker.d.ts +1 -1
- package/dist/auth/worker.d.ts.map +1 -1
- package/dist/auth/worker.js +142 -42
- package/dist/auth/worker.js.map +1 -1
- package/package.json +2 -2
package/dist/auth/worker.js
CHANGED
|
@@ -2,15 +2,18 @@ import { sha256Hex as e } from "../_crypto.js";
|
|
|
2
2
|
import { ApiError as t, badRequest as n, expiredSessionCookie as r, forbidden as i, json as a, noStore as o, queryParams as s, readJsonBody as c, sessionToken as l, toErrorResponse as u, unauthenticated as d } from "../_server/http.js";
|
|
3
3
|
import { serviceRequest as f } from "../_server/service.js";
|
|
4
4
|
import { UUID_RE as p } from "../_server/uuid.js";
|
|
5
|
-
import { ACCOUNT_HEADER as m, ARCHIVE_OPERATOR_ROLE as h, OPERATOR_SESSION_MAX_AGE_SECONDS as g, PUBLISHER_HEADER as _, REQUEST_MARKER_HEADER as v, SESSION_COOKIE_NAME as y, SESSION_MAX_AGE_SECONDS as b, archiveAuthHealthSchema as x, archiveErrorResponseSchema as S, isWellFormedText as
|
|
6
|
-
import { confirmationPage as
|
|
7
|
-
import { authConfig as
|
|
8
|
-
import { AuthOperationError as
|
|
9
|
-
import { LINK_TTL_MS as j, authReadiness as M, consumeToken as N, deleteAccount as P, deleteToken as F, ensureUser as I, insertToken as L, readAccount as R, revokeSessions as z, spendLinkBudget as B, spendSignInBudget as V, sweep as H } from "./store.js";
|
|
5
|
+
import { ACCOUNT_HEADER as m, ARCHIVE_OPERATOR_ROLE as h, OPERATOR_SESSION_MAX_AGE_SECONDS as g, PUBLISHER_HEADER as _, REQUEST_MARKER_HEADER as v, SESSION_COOKIE_NAME as y, SESSION_MAX_AGE_SECONDS as b, archiveAuthHealthSchema as x, archiveErrorResponseSchema as S, isWellFormedText as ee, sessionCookieValue as C, uuidSchema as w } from "../contract.js";
|
|
6
|
+
import { confirmationPage as T, escapeHtml as E } from "./_confirmation.js";
|
|
7
|
+
import { authConfig as D, isWellFormedEmail as O } from "./config.js";
|
|
8
|
+
import { AuthOperationError as k } from "./errors.js";
|
|
9
|
+
import { LINK_TTL_MS as A, approveDevice as j, authReadiness as M, consumeToken as N, deleteAccount as P, deleteToken as F, ensureUser as I, insertToken as L, readAccount as R, revokeSessions as z, spendLinkBudget as B, spendSignInBudget as V, sweep as H } from "./store.js";
|
|
10
10
|
import { SignJWT as U, errors as W, jwtVerify as G } from "jose";
|
|
11
11
|
// src/auth/worker.ts
|
|
12
12
|
var K = {
|
|
13
13
|
account: /^\/api\/v1\/auth\/account$/,
|
|
14
|
+
deviceApprove: /^\/api\/v1\/auth\/device\/approve$/,
|
|
15
|
+
deviceClaim: /^\/api\/v1\/auth\/device\/claim$/,
|
|
16
|
+
deviceSignIn: /^\/api\/v1\/auth\/device\/sign-in$/,
|
|
14
17
|
health: /^\/api\/v1\/auth\/health$/,
|
|
15
18
|
jwks: /^\/api\/v1\/auth\/jwks\.json$/,
|
|
16
19
|
session: /^\/api\/v1\/auth\/session$/,
|
|
@@ -19,6 +22,9 @@ var K = {
|
|
|
19
22
|
verify: /^\/api\/v1\/auth\/verify$/
|
|
20
23
|
}, q = {
|
|
21
24
|
account: ["GET", "DELETE"],
|
|
25
|
+
deviceApprove: ["GET", "POST"],
|
|
26
|
+
deviceClaim: ["POST"],
|
|
27
|
+
deviceSignIn: ["POST"],
|
|
22
28
|
health: ["GET"],
|
|
23
29
|
jwks: ["GET"],
|
|
24
30
|
session: ["GET"],
|
|
@@ -27,13 +33,16 @@ var K = {
|
|
|
27
33
|
verify: ["GET", "POST"]
|
|
28
34
|
}, J = {
|
|
29
35
|
account: "/auth/account",
|
|
36
|
+
deviceApprove: "/auth/device/approve",
|
|
37
|
+
deviceClaim: "/auth/device/claim",
|
|
38
|
+
deviceSignIn: "/auth/device/sign-in",
|
|
30
39
|
health: "/auth/health",
|
|
31
40
|
jwks: "/auth/jwks.json",
|
|
32
41
|
session: "/auth/session",
|
|
33
42
|
signIn: "/auth/sign-in",
|
|
34
43
|
signOutAll: "/auth/sign-out-all",
|
|
35
44
|
verify: "/auth/verify"
|
|
36
|
-
}, matchRoute = (e) => Object.keys(K).find((t) => K[t].test(new URL(e.url).pathname)), Y = 32, X = /^[A-Za-z0-9_-]{43}$/, Z = 256,
|
|
45
|
+
}, matchRoute = (e) => Object.keys(K).find((t) => K[t].test(new URL(e.url).pathname)), Y = 32, X = /^[A-Za-z0-9_-]{43}$/, Z = /^[a-f0-9]{64}$/, Q = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, te = 256, $ = 5, randomToken = () => btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(Y)))).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, ""), unavailable = () => new k(503, "catalog");
|
|
37
46
|
function requireMutationGuard(e, t, n = !1) {
|
|
38
47
|
let r = e.headers.get("origin");
|
|
39
48
|
if (r === null || !t.allowedOrigins.has(r) && !(n && r === t.publicOrigin) || e.headers.get("x-archive-request") !== "1") throw i();
|
|
@@ -41,7 +50,7 @@ function requireMutationGuard(e, t, n = !1) {
|
|
|
41
50
|
}
|
|
42
51
|
function corsHeaders(e, t) {
|
|
43
52
|
let n = e.headers.get("origin");
|
|
44
|
-
return n === null || !t.allowedOrigins.has(n) && (matchRoute(e)
|
|
53
|
+
return n === null || !t.allowedOrigins.has(n) && !(["verify", "deviceApprove"].includes(matchRoute(e) ?? "") && n === t.publicOrigin) ? {} : {
|
|
45
54
|
"access-control-allow-credentials": "true",
|
|
46
55
|
"access-control-allow-origin": n,
|
|
47
56
|
vary: "Origin"
|
|
@@ -82,7 +91,7 @@ async function requireSession(e, t, n) {
|
|
|
82
91
|
let { payload: e } = await G(r, t.verifier, {
|
|
83
92
|
algorithms: ["RS256"],
|
|
84
93
|
audience: t.audience,
|
|
85
|
-
clockTolerance:
|
|
94
|
+
clockTolerance: $,
|
|
86
95
|
issuer: t.issuer,
|
|
87
96
|
maxTokenAge: b,
|
|
88
97
|
requiredClaims: [
|
|
@@ -94,9 +103,9 @@ async function requireSession(e, t, n) {
|
|
|
94
103
|
"sessionVersion"
|
|
95
104
|
]
|
|
96
105
|
}), i = e.sub ?? "";
|
|
97
|
-
if (i.length === 0 || i.length >
|
|
106
|
+
if (i.length === 0 || i.length > te || !ee(i)) throw d();
|
|
98
107
|
let a = e.sessionVersion;
|
|
99
|
-
if (typeof a != "number" || !Number.isSafeInteger(a) || a < 0 || typeof e.iat != "number" || e.iat > Date.now() / 1e3 +
|
|
108
|
+
if (typeof a != "number" || !Number.isSafeInteger(a) || a < 0 || typeof e.iat != "number" || e.iat > Date.now() / 1e3 + $ || Array.isArray(e.roles) && e.roles.includes("archive-operator") && (typeof e.exp != "number" || e.exp - e.iat > 3600 || Date.now() / 1e3 - e.iat > 3600 + $)) throw d();
|
|
100
109
|
let o = await R(n.AUTH_DB, i);
|
|
101
110
|
if (o === null || o.session_version !== a) throw d();
|
|
102
111
|
return {
|
|
@@ -108,42 +117,122 @@ async function requireSession(e, t, n) {
|
|
|
108
117
|
throw e instanceof W.JOSEError ? d() : e;
|
|
109
118
|
}
|
|
110
119
|
}
|
|
111
|
-
async function sendLink(e, t, n, r) {
|
|
112
|
-
let
|
|
120
|
+
async function sendLink(e, t, n, r, i = !1) {
|
|
121
|
+
let a = t.appName, o = `The link works once and expires in ${A / 6e4} minutes. If you did not request it, ignore this email.`, s = i ? `Approve sign-in to ${a} on your app:` : `Open this link to sign in to ${a}:`;
|
|
113
122
|
await e.AUTH_EMAIL.send({
|
|
114
123
|
from: t.senderAddress,
|
|
115
|
-
html: `<p
|
|
116
|
-
subject: `Your ${
|
|
117
|
-
text:
|
|
124
|
+
html: `<p>${E(s)}</p><p><a href="${E(r)}">${E(r)}</a></p><p>${o}</p>`,
|
|
125
|
+
subject: `Your ${a} sign-in link`,
|
|
126
|
+
text: `${s}\n\n${r}\n\n${o}`,
|
|
118
127
|
to: n
|
|
119
128
|
});
|
|
120
129
|
}
|
|
121
|
-
async function handleSignIn(r, i, s) {
|
|
130
|
+
async function handleSignIn(r, i, s, l = !1) {
|
|
122
131
|
requireMutationGuard(r, s);
|
|
123
|
-
let
|
|
124
|
-
if (typeof
|
|
125
|
-
let
|
|
126
|
-
if (
|
|
127
|
-
let
|
|
128
|
-
if (!
|
|
129
|
-
let
|
|
130
|
-
if (
|
|
131
|
-
let
|
|
132
|
-
if (
|
|
133
|
-
let
|
|
134
|
-
await L(i.AUTH_DB,
|
|
132
|
+
let u = await c(r);
|
|
133
|
+
if (typeof u != "object" || !u || Array.isArray(u)) throw n();
|
|
134
|
+
let d = Object.keys(u), f = Reflect.get(u, "email"), p = Reflect.get(u, "challenge");
|
|
135
|
+
if (typeof f != "string" || (l ? d.length !== 2 || typeof p != "string" || !Z.test(p) : d.length !== 1 || d[0] !== "email")) throw n();
|
|
136
|
+
let m = f.trim().toLowerCase();
|
|
137
|
+
if (!O(m)) throw n();
|
|
138
|
+
let h = Date.now(), g = await V(i.AUTH_DB, h);
|
|
139
|
+
if (g !== null) throw new t(429, "rate-limited", "Too many sign-in requests. Try again later.", { retryAfterSeconds: g });
|
|
140
|
+
let _ = await I(i.AUTH_DB, m), v = await B(i.AUTH_DB, _.id, h);
|
|
141
|
+
if (v !== null) throw new t(429, "rate-limited", "Too many sign-in emails. Try again later.", { retryAfterSeconds: v });
|
|
142
|
+
let y = randomToken(), b = await e(y);
|
|
143
|
+
await L(i.AUTH_DB, b, _.id, h);
|
|
135
144
|
try {
|
|
136
|
-
|
|
145
|
+
let e = l ? await new U({
|
|
146
|
+
challenge: p,
|
|
147
|
+
token: y
|
|
148
|
+
}).setProtectedHeader({
|
|
149
|
+
alg: "RS256",
|
|
150
|
+
kid: s.signing.kid,
|
|
151
|
+
typ: "archive-device-approval"
|
|
152
|
+
}).setIssuedAt().setIssuer(s.issuer).setAudience(s.audience).setExpirationTime(`${A / 1e3}s`).sign(s.signing.key) : y, t = l ? "device/approve" : "verify";
|
|
153
|
+
await sendLink(i, s, _.email, `${s.publicOrigin}/api/v1/auth/${t}?token=${e}`, l);
|
|
137
154
|
} catch (e) {
|
|
138
|
-
throw await F(i.AUTH_DB,
|
|
155
|
+
throw await F(i.AUTH_DB, b).catch(() => void 0), new k(503, "email", { cause: e });
|
|
139
156
|
}
|
|
140
157
|
return a(202, { ok: !0 }, o);
|
|
141
158
|
}
|
|
159
|
+
async function handleDeviceApprove(r, i, a) {
|
|
160
|
+
let l;
|
|
161
|
+
if (r.method === "GET") l = s(new URL(r.url), ["token"]).get("token") ?? "";
|
|
162
|
+
else {
|
|
163
|
+
requireMutationGuard(r, a, !0), s(new URL(r.url), []);
|
|
164
|
+
let e = await c(r);
|
|
165
|
+
if (typeof e != "object" || !e || Array.isArray(e) || Object.keys(e).length !== 1) throw n("The link is not valid.");
|
|
166
|
+
l = Reflect.get(e, "token");
|
|
167
|
+
}
|
|
168
|
+
if (typeof l != "string" || l.length > 2048 || !Q.test(l)) throw n("The link is not valid.");
|
|
169
|
+
let u, d;
|
|
170
|
+
try {
|
|
171
|
+
let e = await G(l, a.verifier, {
|
|
172
|
+
algorithms: ["RS256"],
|
|
173
|
+
audience: a.audience,
|
|
174
|
+
issuer: a.issuer,
|
|
175
|
+
maxTokenAge: `${A / 1e3}s`,
|
|
176
|
+
requiredClaims: [
|
|
177
|
+
"iss",
|
|
178
|
+
"aud",
|
|
179
|
+
"iat",
|
|
180
|
+
"exp"
|
|
181
|
+
],
|
|
182
|
+
typ: "archive-device-approval"
|
|
183
|
+
});
|
|
184
|
+
u = e.payload.token, d = e.payload.challenge;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
throw e instanceof W.JOSEError ? new t(410, "gone", "That link has expired or was already used.") : e;
|
|
187
|
+
}
|
|
188
|
+
if (typeof u != "string" || !X.test(u) || typeof d != "string" || !Z.test(d)) throw n("The link is not valid.");
|
|
189
|
+
if (r.method === "GET") return T({
|
|
190
|
+
...a,
|
|
191
|
+
device: !0,
|
|
192
|
+
deviceCode: d.slice(0, 8).toUpperCase(),
|
|
193
|
+
nonce: randomToken(),
|
|
194
|
+
token: l
|
|
195
|
+
});
|
|
196
|
+
if (!await j(i.AUTH_DB, await e(u), await e(`device:${d}`), Date.now())) throw new t(410, "gone", "That link has expired or was already used.");
|
|
197
|
+
return new Response(null, {
|
|
198
|
+
headers: o,
|
|
199
|
+
status: 204
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function handleDeviceClaim(r, i, u) {
|
|
203
|
+
requireMutationGuard(r, u), s(new URL(r.url), []);
|
|
204
|
+
let d = await c(r);
|
|
205
|
+
if (typeof d != "object" || !d || Array.isArray(d) || Object.keys(d).length !== 1) throw n();
|
|
206
|
+
let f = Reflect.get(d, "token");
|
|
207
|
+
if (typeof f != "string" || !X.test(f)) throw n();
|
|
208
|
+
if (l(r) !== null) {
|
|
209
|
+
let e = !1;
|
|
210
|
+
try {
|
|
211
|
+
await requireSession(r, u, i), e = !0;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
if (!(e instanceof t && e.status === 401)) throw e;
|
|
214
|
+
}
|
|
215
|
+
if (e) throw new t(409, "account-changed", "Sign out before claiming a device sign-in.");
|
|
216
|
+
}
|
|
217
|
+
let p = await N(i.AUTH_DB, await e(`device:${await e(f)}`), Date.now());
|
|
218
|
+
if (p === null) return a(202, { ok: !1 }, o);
|
|
219
|
+
let m = await mintSession(u, p.id, u.operatorEmails.has(p.email), p.sessionVersion);
|
|
220
|
+
return new Response(null, {
|
|
221
|
+
headers: {
|
|
222
|
+
...o,
|
|
223
|
+
"set-cookie": C(m.token, {
|
|
224
|
+
maxAgeSeconds: m.maxAge,
|
|
225
|
+
secure: secure(u)
|
|
226
|
+
})
|
|
227
|
+
},
|
|
228
|
+
status: 204
|
|
229
|
+
});
|
|
230
|
+
}
|
|
142
231
|
async function handleVerify(r, i, a) {
|
|
143
232
|
if (r.method === "GET") {
|
|
144
233
|
let e = s(new URL(r.url), ["token"]).get("token") ?? "";
|
|
145
234
|
if (!X.test(e)) throw n("The link is not valid.");
|
|
146
|
-
return
|
|
235
|
+
return T({
|
|
147
236
|
...a,
|
|
148
237
|
nonce: randomToken(),
|
|
149
238
|
token: e
|
|
@@ -160,7 +249,7 @@ async function handleVerify(r, i, a) {
|
|
|
160
249
|
return new Response(null, {
|
|
161
250
|
headers: {
|
|
162
251
|
...o,
|
|
163
|
-
"set-cookie":
|
|
252
|
+
"set-cookie": C(f.token, {
|
|
164
253
|
maxAgeSeconds: f.maxAge,
|
|
165
254
|
secure: secure(a)
|
|
166
255
|
})
|
|
@@ -199,7 +288,7 @@ async function archiveFailure(e) {
|
|
|
199
288
|
}
|
|
200
289
|
async function handleErase(e, i, a) {
|
|
201
290
|
let s = requireMutationGuard(e, a), { subject: c, token: l } = await requireSession(e, a, i), u = e.headers.get(m);
|
|
202
|
-
if (u === null || !
|
|
291
|
+
if (u === null || !w.safeParse(u).success) throw n();
|
|
203
292
|
if (u !== c) throw new t(409, "account-changed", "The signed-in account changed since this request was prepared.");
|
|
204
293
|
let d = new Headers({
|
|
205
294
|
cookie: `${y}=${l}`,
|
|
@@ -218,7 +307,7 @@ async function handleErase(e, i, a) {
|
|
|
218
307
|
redirect: "manual"
|
|
219
308
|
}), async (e) => e.status === 204 ? (await e.body?.cancel(), e) : archiveFailure(e));
|
|
220
309
|
} catch (e) {
|
|
221
|
-
throw new
|
|
310
|
+
throw new k(503, "catalog", { cause: e });
|
|
222
311
|
}
|
|
223
312
|
return g.status === 204 ? (await P(i.AUTH_DB, c), new Response(null, {
|
|
224
313
|
headers: {
|
|
@@ -231,7 +320,7 @@ async function handleErase(e, i, a) {
|
|
|
231
320
|
async function handleSignOutAll(e, i, s) {
|
|
232
321
|
requireMutationGuard(e, s);
|
|
233
322
|
let { subject: c, sessionVersion: l } = await requireSession(e, s, i), u = e.headers.get(m);
|
|
234
|
-
if (u === null || !
|
|
323
|
+
if (u === null || !w.safeParse(u).success) throw n();
|
|
235
324
|
if (u !== c) throw new t(409, "account-changed", "The signed-in account changed since this request was prepared.");
|
|
236
325
|
if (await z(i.AUTH_DB, c, l) === null) throw d();
|
|
237
326
|
return a(200, { ok: !0 }, {
|
|
@@ -242,7 +331,7 @@ async function handleSignOutAll(e, i, s) {
|
|
|
242
331
|
async function handleHealth(e) {
|
|
243
332
|
let t = null, n = null;
|
|
244
333
|
try {
|
|
245
|
-
await
|
|
334
|
+
await D(e);
|
|
246
335
|
} catch {
|
|
247
336
|
t = "configuration-unavailable";
|
|
248
337
|
}
|
|
@@ -262,11 +351,20 @@ async function route(e, n) {
|
|
|
262
351
|
let r = e.method.toUpperCase(), i = matchRoute(e);
|
|
263
352
|
if (i === void 0) throw new t(404, "not-found", "No such route.");
|
|
264
353
|
if (i === "health" && r === "GET") return handleHealth(n);
|
|
265
|
-
let s = await
|
|
354
|
+
let s = await D(n), c = corsHeaders(e, s);
|
|
266
355
|
if (r === "OPTIONS") return preflight(e, s, q[i]);
|
|
267
356
|
if (!q[i].includes(r)) throw new t(405, "method-not-allowed", "That method is not allowed here.");
|
|
268
357
|
let l;
|
|
269
358
|
switch (i) {
|
|
359
|
+
case "deviceApprove":
|
|
360
|
+
l = await handleDeviceApprove(e, n, s);
|
|
361
|
+
break;
|
|
362
|
+
case "deviceClaim":
|
|
363
|
+
l = await handleDeviceClaim(e, n, s);
|
|
364
|
+
break;
|
|
365
|
+
case "deviceSignIn":
|
|
366
|
+
l = await handleSignIn(e, n, s, !0);
|
|
367
|
+
break;
|
|
270
368
|
case "health":
|
|
271
369
|
l = await handleHealth(n);
|
|
272
370
|
break;
|
|
@@ -295,7 +393,7 @@ function withCors(e, t) {
|
|
|
295
393
|
for (let [e, r] of Object.entries(t)) n.headers.set(e, r);
|
|
296
394
|
return n;
|
|
297
395
|
}
|
|
298
|
-
var diagnostic = (e) => e instanceof
|
|
396
|
+
var diagnostic = (e) => e instanceof k ? e.authDiagnostic : { kind: "internal" };
|
|
299
397
|
function createAuthWorker() {
|
|
300
398
|
let e = /* @__PURE__ */ new Set(), emit = (t) => {
|
|
301
399
|
for (let n of e) try {
|
|
@@ -309,14 +407,16 @@ function createAuthWorker() {
|
|
|
309
407
|
o = await route(n, r);
|
|
310
408
|
} catch (e) {
|
|
311
409
|
s = e, o = u(e, i);
|
|
312
|
-
let a = await
|
|
313
|
-
a !== null && (matchRoute(n) === "verify" && n.method === "GET" && e instanceof t && e.status === 400 && (o =
|
|
410
|
+
let a = await D(r).catch(() => null);
|
|
411
|
+
a !== null && ((matchRoute(n) === "verify" || matchRoute(n) === "deviceApprove") && n.method === "GET" && e instanceof t && (e.status === 400 || matchRoute(n) === "deviceApprove" && e.status === 410) && (o = T({
|
|
314
412
|
...a,
|
|
413
|
+
device: matchRoute(n) === "deviceApprove",
|
|
414
|
+
errorStatus: e.status === 410 ? 410 : 400,
|
|
315
415
|
nonce: randomToken(),
|
|
316
416
|
token: null
|
|
317
417
|
})), o = withCors(o, corsHeaders(n, a)));
|
|
318
418
|
}
|
|
319
|
-
if (matchRoute(n) === "verify" && (o.headers.set("cache-control", "no-store"), o.headers.set("referrer-policy", "no-referrer"), o.headers.set("x-content-type-options", "nosniff"), o.headers.has("content-security-policy") || o.headers.set("content-security-policy", "default-src 'none'; base-uri 'none'; frame-ancestors 'none'")), e.size > 0) {
|
|
419
|
+
if ((matchRoute(n) === "verify" || matchRoute(n) === "deviceApprove") && (o.headers.set("cache-control", "no-store"), o.headers.set("referrer-policy", "no-referrer"), o.headers.set("x-content-type-options", "nosniff"), o.headers.has("content-security-policy") || o.headers.set("content-security-policy", "default-src 'none'; base-uri 'none'; frame-ancestors 'none'")), e.size > 0) {
|
|
320
420
|
let e = matchRoute(n), r = o.status < 400 ? "ok" : e === "health" ? "unavailable" : s instanceof t ? s.code : "internal", c = s === void 0 && o.status >= 400 ? S.safeParse(await o.clone().json().catch(() => null)) : null, l = e === "health" && o.status === 503 ? x.safeParse(await o.clone().json().catch(() => null)) : null;
|
|
321
421
|
emit({
|
|
322
422
|
code: c?.success ? c.data.error.code : r,
|
|
@@ -332,7 +432,7 @@ function createAuthWorker() {
|
|
|
332
432
|
},
|
|
333
433
|
async scheduled(t, n) {
|
|
334
434
|
try {
|
|
335
|
-
await
|
|
435
|
+
await D(n);
|
|
336
436
|
let t = await H(n.AUTH_DB, Date.now());
|
|
337
437
|
e.size > 0 && emit({
|
|
338
438
|
removed: t,
|
package/dist/auth/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","names":["ROUTES","ROUTE_METHODS","ROUTE_LABELS","id","request","TOKEN_BYTES","LINK_TOKEN","SUBJECT_MAX","CLOCK_TOLERANCE_SECONDS","AuthOperationError","origin","config","allowPublicOrigin","forbidden","cors","ACCOUNT_HEADER","PUBLISHER_HEADER","REQUEST_MARKER_HEADER","methods","maxAge","operator","OPERATOR_SESSION_MAX_AGE_SECONDS","SESSION_MAX_AGE_SECONDS","SignJWT","ARCHIVE_OPERATOR_ROLE","subject","token","sessionToken","unauthenticated","jwtVerify","payload","isWellFormedText","version","account","readAccount","env","error","errors","name","notice","LINK_TTL_MS","escapeHtml","link","body","readJsonBody","badRequest","keys","requestedEmail","email","isWellFormedEmail","nowMs","admissionRetry","spendSignInBudget","ApiError","user","ensureUser","retryAfterSeconds","spendLinkBudget","tokenHash","sha256Hex","insertToken","deleteToken","json","noStore","queryParams","confirmationPage","consumeToken","session","sessionCookieValue","row","text","response","parsed","envelope","archiveErrorResponseSchema","retryAfter","uuidSchema","headers","SESSION_COOKIE_NAME","publisher","UUID_RE","archived","serviceRequest","deleteAccount","expiredSessionCookie","revokeSessions","sessionVersion","reason","dbSchemaVersion","authConfig","authReadiness","archiveAuthHealthSchema","method","routeId","next","key","value","tappers","handler","event","requestId","started","failure","toErrorResponse","code","forwardedCode","health","removed","sweep","options"],"sources":["../../src/auth/worker.ts"],"sourcesContent":["import { errors, jwtVerify, SignJWT } from 'jose';\nimport { sha256Hex } from '../_crypto';\nimport type { ArchiveExecutionContext } from '../_server/execution';\nimport {\n ApiError,\n badRequest,\n expiredSessionCookie,\n forbidden,\n json,\n noStore,\n queryParams,\n readJsonBody,\n sessionToken,\n toErrorResponse,\n unauthenticated,\n} from '../_server/http';\nimport { serviceRequest } from '../_server/service';\nimport { UUID_RE } from '../_server/uuid';\nimport {\n ACCOUNT_HEADER,\n ARCHIVE_OPERATOR_ROLE,\n type ArchiveErrorCode,\n AUTH_SCHEMA_VERSION,\n archiveAuthHealthSchema,\n archiveErrorResponseSchema,\n isWellFormedText,\n OPERATOR_SESSION_MAX_AGE_SECONDS,\n PUBLISHER_HEADER,\n REQUEST_MARKER_HEADER,\n SESSION_COOKIE_NAME,\n SESSION_MAX_AGE_SECONDS,\n sessionCookieValue,\n uuidSchema,\n} from '../contract';\nimport { confirmationPage, escapeHtml } from './_confirmation';\nimport { type AuthConfig, type AuthWorkerEnv, authConfig, isWellFormedEmail } from './config';\nimport { type AuthDiagnostic, AuthOperationError } from './errors';\nimport {\n authReadiness,\n consumeToken,\n deleteAccount,\n deleteToken,\n ensureUser,\n insertToken,\n LINK_TTL_MS,\n readAccount,\n revokeSessions,\n spendLinkBudget,\n spendSignInBudget,\n sweep,\n} from './store';\n\n/**\n * The reference identity issuer: passwordless email accounts. A POST with an email\n * address ensures the account row and sends a single-use magic link; consuming it\n * signs the browser in by setting the `archive_session` cookie a catalog Worker\n * verifies — an RS256 JWT minted by the active private key, verifiable through\n * `/jwks.json` alongside any configured retired public keys. `OPERATOR_EMAILS` is\n * the one operator-membership truth; operator sessions expire after one hour,\n * bounding authority from previously minted role claims. The account routes export the issuer's\n * own record and erase it together with the catalog profile.\n */\nexport interface AuthWorkerHandler<E extends AuthWorkerEnv = AuthWorkerEnv> {\n fetch(request: Request, env: E, ctx?: ArchiveExecutionContext): Promise<Response>;\n /** The cron sweep: expired links, closed send windows and never-verified accounts. */\n scheduled(controller: unknown, env: E, ctx?: ArchiveExecutionContext): Promise<void>;\n tap(handler: (event: AuthEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;\n}\n\nexport type AuthEvent =\n | {\n readonly type: 'request';\n readonly requestId: string;\n readonly route:\n | '/auth/account'\n | '/auth/jwks.json'\n | '/auth/sign-in'\n | '/auth/verify'\n | '/auth/session'\n | '/auth/sign-out-all'\n | '/auth/health'\n | 'unmatched';\n readonly status: number;\n readonly code: ArchiveErrorCode | 'ok';\n readonly ms: number;\n readonly diagnostic?: AuthDiagnostic;\n }\n | { readonly type: 'scheduled'; readonly status: 'ok'; readonly removed: number }\n | { readonly type: 'scheduled'; readonly status: 'failed'; readonly diagnostic: AuthDiagnostic };\n\nconst ROUTES = {\n account: /^\\/api\\/v1\\/auth\\/account$/,\n health: /^\\/api\\/v1\\/auth\\/health$/,\n jwks: /^\\/api\\/v1\\/auth\\/jwks\\.json$/,\n session: /^\\/api\\/v1\\/auth\\/session$/,\n signIn: /^\\/api\\/v1\\/auth\\/sign-in$/,\n signOutAll: /^\\/api\\/v1\\/auth\\/sign-out-all$/,\n verify: /^\\/api\\/v1\\/auth\\/verify$/,\n} as const;\n\nconst ROUTE_METHODS: Record<keyof typeof ROUTES, readonly string[]> = {\n account: ['GET', 'DELETE'],\n health: ['GET'],\n jwks: ['GET'],\n session: ['GET'],\n signIn: ['POST'],\n signOutAll: ['POST'],\n verify: ['GET', 'POST'],\n};\n\nconst ROUTE_LABELS = {\n account: '/auth/account',\n health: '/auth/health',\n jwks: '/auth/jwks.json',\n session: '/auth/session',\n signIn: '/auth/sign-in',\n signOutAll: '/auth/sign-out-all',\n verify: '/auth/verify',\n} as const;\n\nconst matchRoute = (request: Request): keyof typeof ROUTES | undefined =>\n (Object.keys(ROUTES) as (keyof typeof ROUTES)[]).find((id) => ROUTES[id].test(new URL(request.url).pathname));\n\nconst TOKEN_BYTES = 32;\n// Base64url of TOKEN_BYTES random bytes, unpadded.\nconst LINK_TOKEN = /^[A-Za-z0-9_-]{43}$/;\nconst SUBJECT_MAX = 256;\nconst CLOCK_TOLERANCE_SECONDS = 5;\n\nconst randomToken = (): string =>\n btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(TOKEN_BYTES))))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replace(/=+$/, '');\n\nconst unavailable = (): ApiError => new AuthOperationError(503, 'catalog');\n\n/** Exact allowed Origin plus the request marker, like the catalog's mutation guard. */\nfunction requireMutationGuard(request: Request, config: AuthConfig, allowPublicOrigin = false): string {\n const origin = request.headers.get('origin');\n if (origin === null || (!config.allowedOrigins.has(origin) && !(allowPublicOrigin && origin === config.publicOrigin)))\n throw forbidden();\n if (request.headers.get(REQUEST_MARKER_HEADER) !== '1') throw forbidden();\n return origin;\n}\n\nfunction corsHeaders(request: Request, config: AuthConfig): Record<string, string> {\n const origin = request.headers.get('origin');\n if (\n origin === null ||\n (!config.allowedOrigins.has(origin) && !(matchRoute(request) === 'verify' && origin === config.publicOrigin))\n )\n return {};\n return { 'access-control-allow-credentials': 'true', 'access-control-allow-origin': origin, vary: 'Origin' };\n}\n\nfunction preflight(request: Request, config: AuthConfig, methods: readonly string[]): Response {\n const cors = corsHeaders(request, config);\n if (Object.keys(cors).length === 0) throw forbidden();\n return new Response(null, {\n headers: {\n ...cors,\n 'access-control-allow-headers': `Content-Type, ${ACCOUNT_HEADER}, ${PUBLISHER_HEADER}, ${REQUEST_MARKER_HEADER}`,\n 'access-control-allow-methods': [...methods, 'OPTIONS'].join(', '),\n 'access-control-max-age': '600',\n },\n status: 204,\n });\n}\n\nconst secure = (config: AuthConfig): boolean => config.appEnv === 'production';\n\nasync function mintSession(\n config: AuthConfig,\n subject: string,\n operator: boolean,\n sessionVersion: number,\n): Promise<{ maxAge: number; token: string }> {\n const maxAge = operator ? OPERATOR_SESSION_MAX_AGE_SECONDS : SESSION_MAX_AGE_SECONDS;\n const token = await new SignJWT({ sessionVersion, ...(operator ? { roles: [ARCHIVE_OPERATOR_ROLE] } : {}) })\n .setProtectedHeader({ alg: 'RS256', kid: config.signing.kid, typ: 'JWT' })\n .setIssuedAt()\n .setJti(crypto.randomUUID())\n .setIssuer(config.issuer)\n .setAudience(config.audience)\n .setSubject(subject)\n .setExpirationTime(`${maxAge}s`)\n .sign(config.signing.key);\n return { maxAge, token };\n}\n\n/** Verifies the request's own session cookie against every published key; returns the token and subject. */\nasync function requireSession(\n request: Request,\n config: AuthConfig,\n env: AuthWorkerEnv,\n): Promise<{ subject: string; token: string; sessionVersion: number }> {\n const token = sessionToken(request);\n if (token === null) throw unauthenticated();\n try {\n const { payload } = await jwtVerify(token, config.verifier, {\n algorithms: ['RS256'],\n audience: config.audience,\n clockTolerance: CLOCK_TOLERANCE_SECONDS,\n issuer: config.issuer,\n maxTokenAge: SESSION_MAX_AGE_SECONDS,\n requiredClaims: ['sub', 'iss', 'aud', 'iat', 'exp', 'sessionVersion'],\n });\n const subject = payload.sub ?? '';\n if (subject.length === 0 || subject.length > SUBJECT_MAX || !isWellFormedText(subject)) throw unauthenticated();\n const version = payload.sessionVersion;\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) throw unauthenticated();\n if (typeof payload.iat !== 'number' || payload.iat > Date.now() / 1000 + CLOCK_TOLERANCE_SECONDS)\n throw unauthenticated();\n if (\n Array.isArray(payload.roles) &&\n payload.roles.includes(ARCHIVE_OPERATOR_ROLE) &&\n (typeof payload.exp !== 'number' ||\n payload.exp - payload.iat > OPERATOR_SESSION_MAX_AGE_SECONDS ||\n Date.now() / 1000 - payload.iat > OPERATOR_SESSION_MAX_AGE_SECONDS + CLOCK_TOLERANCE_SECONDS)\n )\n throw unauthenticated();\n const account = await readAccount(env.AUTH_DB, subject);\n if (account === null || account.session_version !== version) throw unauthenticated();\n return { sessionVersion: version, subject, token };\n } catch (error) {\n if (error instanceof errors.JOSEError) throw unauthenticated();\n throw error;\n }\n}\n\nasync function sendLink(env: AuthWorkerEnv, config: AuthConfig, to: string, link: string): Promise<void> {\n const name = config.appName;\n const minutes = LINK_TTL_MS / 60_000;\n const notice = `The link works once and expires in ${minutes} minutes. If you did not request it, ignore this email.`;\n await env.AUTH_EMAIL.send({\n from: config.senderAddress,\n html: `<p>Open this link to sign in to ${escapeHtml(name)}:</p><p><a href=\"${escapeHtml(link)}\">${escapeHtml(link)}</a></p><p>${notice}</p>`,\n subject: `Your ${name} sign-in link`,\n text: `Open this link to sign in to ${name}:\\n\\n${link}\\n\\n${notice}`,\n to,\n });\n}\n\nasync function handleSignIn(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n requireMutationGuard(request, config);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body)) throw badRequest();\n const keys = Object.keys(body);\n const requestedEmail: unknown = Reflect.get(body, 'email');\n if (keys.length !== 1 || keys[0] !== 'email' || typeof requestedEmail !== 'string') {\n throw badRequest();\n }\n const email = requestedEmail.trim().toLowerCase();\n if (!isWellFormedEmail(email)) {\n throw badRequest();\n }\n const nowMs = Date.now();\n const admissionRetry = await spendSignInBudget(env.AUTH_DB, nowMs);\n if (admissionRetry !== null) {\n throw new ApiError(429, 'rate-limited', 'Too many sign-in requests. Try again later.', {\n retryAfterSeconds: admissionRetry,\n });\n }\n const user = await ensureUser(env.AUTH_DB, email);\n const retryAfterSeconds = await spendLinkBudget(env.AUTH_DB, user.id, nowMs);\n if (retryAfterSeconds !== null) {\n throw new ApiError(429, 'rate-limited', 'Too many sign-in emails. Try again later.', { retryAfterSeconds });\n }\n const token = randomToken();\n const tokenHash = await sha256Hex(token);\n await insertToken(env.AUTH_DB, tokenHash, user.id, nowMs);\n try {\n await sendLink(env, config, user.email, `${config.publicOrigin}/api/v1/auth/verify?token=${token}`);\n } catch (error) {\n // An unsent link is useless; the spent allowance is deliberately not refunded.\n await deleteToken(env.AUTH_DB, tokenHash).catch(() => undefined);\n throw new AuthOperationError(503, 'email', { cause: error });\n }\n return json(202, { ok: true }, noStore);\n}\n\nasync function handleVerify(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n if (request.method === 'GET') {\n const token = queryParams(new URL(request.url), ['token']).get('token') ?? '';\n if (!LINK_TOKEN.test(token)) throw badRequest('The link is not valid.');\n return confirmationPage({ ...config, nonce: randomToken(), token });\n }\n requireMutationGuard(request, config, true);\n queryParams(new URL(request.url), []);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).length !== 1) {\n throw badRequest('The link is not valid.');\n }\n const token: unknown = Reflect.get(body, 'token');\n if (typeof token !== 'string') throw badRequest('The link is not valid.');\n if (!LINK_TOKEN.test(token)) throw badRequest('The link is not valid.');\n const user = await consumeToken(env.AUTH_DB, await sha256Hex(token), Date.now());\n if (user === null) throw new ApiError(410, 'gone', 'That link has expired or was already used.');\n const session = await mintSession(config, user.id, config.operatorEmails.has(user.email), user.sessionVersion);\n return new Response(null, {\n headers: {\n ...noStore,\n 'set-cookie': sessionCookieValue(session.token, { maxAgeSeconds: session.maxAge, secure: secure(config) }),\n },\n status: 204,\n });\n}\n\n/** The issuer's half of a complete export: the account record only, never tokens or budget state. */\nasync function handleExport(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n const { subject } = await requireSession(request, config, env);\n const row = await readAccount(env.AUTH_DB, subject);\n if (row === null) throw unauthenticated();\n return json(\n 200,\n { createdAt: row.created_at, email: row.email, id: row.id, lastSignInAt: row.last_sign_in_at },\n noStore,\n );\n}\n\n/** Re-serializes a failed catalog response into the public envelope; anything unrecognizable is 503. */\nasync function archiveFailure(response: Response): Promise<Response> {\n const text = await response.text().catch(() => '');\n let parsed: unknown = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = null;\n }\n const envelope = archiveErrorResponseSchema.safeParse(parsed);\n if (!envelope.success || response.status < 400 || response.status > 599) throw unavailable();\n const { code, message, requestId } = envelope.data.error;\n const retryAfter = response.headers.get('retry-after');\n return json(\n response.status,\n { error: { code, message, requestId } },\n retryAfter !== null && /^\\d{1,6}$/.test(retryAfter) ? { ...noStore, 'retry-after': retryAfter } : noStore,\n );\n}\n\n/**\n * Full erasure: catalog session revocation and profile erasure first (through the AUTH_ARCHIVE service binding,\n * presenting the caller's own session), then the issuer's account row, and only then\n * the cookie is expired. Any failure keeps the browser session so the same request\n * can be retried; both halves are idempotent, so a retry after a partial success\n * re-runs the catalog no-op and finishes the issuer side. The account precondition\n * pins every attempt to the confirmed issuer subject, even without a publisher.\n */\nasync function handleErase(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n const origin = requireMutationGuard(request, config);\n const { subject, token } = await requireSession(request, config, env);\n const account = request.headers.get(ACCOUNT_HEADER);\n if (account === null || !uuidSchema.safeParse(account).success) throw badRequest();\n if (account !== subject) {\n throw new ApiError(409, 'account-changed', 'The signed-in account changed since this request was prepared.');\n }\n const headers = new Headers({\n cookie: `${SESSION_COOKIE_NAME}=${token}`,\n origin,\n [REQUEST_MARKER_HEADER]: '1',\n });\n const publisher = request.headers.get(PUBLISHER_HEADER);\n if (publisher !== null) {\n if (!UUID_RE.test(publisher)) throw badRequest();\n headers.set(PUBLISHER_HEADER, publisher);\n }\n let archived: Response;\n try {\n archived = await serviceRequest(\n env.AUTH_ARCHIVE,\n new Request(`${config.publicOrigin}/api/v1/me/account`, { headers, method: 'DELETE', redirect: 'manual' }),\n async (response) => {\n if (response.status === 204) {\n await response.body?.cancel();\n return response;\n }\n return archiveFailure(response);\n },\n );\n } catch (error) {\n throw new AuthOperationError(503, 'catalog', { cause: error });\n }\n if (archived.status !== 204) return archived;\n await deleteAccount(env.AUTH_DB, subject);\n return new Response(null, {\n headers: { ...noStore, 'set-cookie': expiredSessionCookie(secure(config)) },\n status: 204,\n });\n}\n\nasync function handleSignOutAll(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n requireMutationGuard(request, config);\n const { subject, sessionVersion } = await requireSession(request, config, env);\n const account = request.headers.get(ACCOUNT_HEADER);\n if (account === null || !uuidSchema.safeParse(account).success) throw badRequest();\n if (account !== subject)\n throw new ApiError(409, 'account-changed', 'The signed-in account changed since this request was prepared.');\n if ((await revokeSessions(env.AUTH_DB, subject, sessionVersion)) === null) throw unauthenticated();\n return json(200, { ok: true }, { ...noStore, 'set-cookie': expiredSessionCookie(secure(config)) });\n}\n\nasync function handleHealth(env: AuthWorkerEnv): Promise<Response> {\n let reason: 'configuration-unavailable' | 'database-unavailable' | 'schema-mismatch' | null = null;\n let dbSchemaVersion: number | null = null;\n try {\n await authConfig(env);\n } catch {\n reason = 'configuration-unavailable';\n }\n if (reason === null) {\n try {\n dbSchemaVersion = await authReadiness(env.AUTH_DB);\n if (dbSchemaVersion !== AUTH_SCHEMA_VERSION) reason = 'schema-mismatch';\n } catch {\n reason = 'database-unavailable';\n }\n }\n return json(\n reason === null ? 200 : 503,\n archiveAuthHealthSchema.parse({\n dbSchemaVersion,\n reason,\n schemaVersion: AUTH_SCHEMA_VERSION,\n status: reason === null ? 'ok' : 'degraded',\n }),\n noStore,\n );\n}\nasync function route(request: Request, env: AuthWorkerEnv): Promise<Response> {\n const method = request.method.toUpperCase();\n const routeId = matchRoute(request);\n if (routeId === undefined) throw new ApiError(404, 'not-found', 'No such route.');\n if (routeId === 'health' && method === 'GET') return handleHealth(env);\n const config = await authConfig(env);\n const cors = corsHeaders(request, config);\n if (method === 'OPTIONS') return preflight(request, config, ROUTE_METHODS[routeId]);\n if (!ROUTE_METHODS[routeId].includes(method)) {\n throw new ApiError(405, 'method-not-allowed', 'That method is not allowed here.');\n }\n let response: Response;\n switch (routeId) {\n case 'health':\n response = await handleHealth(env);\n break;\n case 'session':\n await requireSession(request, config, env);\n response = json(200, { ok: true }, noStore);\n break;\n case 'signOutAll':\n response = await handleSignOutAll(request, env, config);\n break;\n case 'jwks':\n response = json(200, { keys: config.jwks }, noStore);\n break;\n case 'signIn':\n response = await handleSignIn(request, env, config);\n break;\n case 'verify':\n response = await handleVerify(request, env, config);\n break;\n case 'account':\n response = method === 'GET' ? await handleExport(request, env, config) : await handleErase(request, env, config);\n break;\n }\n return withCors(response, cors);\n}\n\nfunction withCors(response: Response, cors: Record<string, string>): Response {\n if (Object.keys(cors).length === 0) return response;\n const next = new Response(response.body, response);\n for (const [key, value] of Object.entries(cors)) next.headers.set(key, value);\n return next;\n}\n\nconst diagnostic = (error: unknown): AuthDiagnostic =>\n error instanceof AuthOperationError ? error.authDiagnostic : { kind: 'internal' };\n\n/**\n * Builds the reference identity issuer. Route behavior is fixed; every deployment\n * difference is validated configuration — the app name, trusted origins, audience and\n * issuer claims, signing keys, operator roster, the catalog service binding, and\n * where links and redirects land.\n */\nexport function createAuthWorker<E extends AuthWorkerEnv>(): AuthWorkerHandler<E> {\n const tappers = new Set<(event: AuthEvent) => void>();\n const emit = (event: AuthEvent): void => {\n for (const handler of tappers) {\n try {\n handler(event);\n } catch {\n // Observers never change request or scheduled outcomes.\n }\n }\n };\n return {\n async fetch(request: Request, env: E): Promise<Response> {\n const requestId = crypto.randomUUID();\n const started = Date.now();\n let response: Response;\n let failure: unknown;\n try {\n response = await route(request, env);\n } catch (error) {\n failure = error;\n response = toErrorResponse(error, requestId);\n // Failures stay readable by allowed origins; config faults have no trusted origin set.\n const config = await authConfig(env).catch(() => null);\n if (config !== null) {\n if (\n matchRoute(request) === 'verify' &&\n request.method === 'GET' &&\n error instanceof ApiError &&\n error.status === 400\n )\n response = confirmationPage({ ...config, nonce: randomToken(), token: null });\n response = withCors(response, corsHeaders(request, config));\n }\n }\n if (matchRoute(request) === 'verify') {\n response.headers.set('cache-control', 'no-store');\n response.headers.set('referrer-policy', 'no-referrer');\n response.headers.set('x-content-type-options', 'nosniff');\n if (!response.headers.has('content-security-policy')) {\n response.headers.set(\n 'content-security-policy',\n \"default-src 'none'; base-uri 'none'; frame-ancestors 'none'\",\n );\n }\n }\n if (tappers.size > 0) {\n const routeId = matchRoute(request);\n const code =\n response.status < 400\n ? 'ok'\n : routeId === 'health'\n ? 'unavailable'\n : failure instanceof ApiError\n ? failure.code\n : 'internal';\n // Account erasure may forward a catalog error response rather than throw.\n const forwardedCode =\n failure === undefined && response.status >= 400\n ? archiveErrorResponseSchema.safeParse(\n await response\n .clone()\n .json()\n .catch(() => null),\n )\n : null;\n const health =\n routeId === 'health' && response.status === 503\n ? archiveAuthHealthSchema.safeParse(\n await response\n .clone()\n .json()\n .catch(() => null),\n )\n : null;\n emit({\n code: forwardedCode?.success ? forwardedCode.data.error.code : code,\n ms: Math.max(0, Date.now() - started),\n requestId,\n route: routeId === undefined ? 'unmatched' : ROUTE_LABELS[routeId],\n status: response.status,\n type: 'request',\n ...(response.status >= 500\n ? {\n diagnostic: health?.success\n ? {\n kind:\n health.data.reason === 'configuration-unavailable'\n ? 'configuration'\n : health.data.reason === 'schema-mismatch'\n ? 'schema'\n : 'database',\n }\n : failure === undefined\n ? { kind: 'catalog' as const }\n : diagnostic(failure),\n }\n : {}),\n });\n }\n return response;\n },\n\n async scheduled(_controller: unknown, env: E): Promise<void> {\n try {\n await authConfig(env);\n const removed = await sweep(env.AUTH_DB, Date.now());\n if (tappers.size > 0) emit({ removed, status: 'ok', type: 'scheduled' });\n } catch (error) {\n if (tappers.size > 0) emit({ diagnostic: diagnostic(error), status: 'failed', type: 'scheduled' });\n throw error;\n }\n },\n tap(handler, options) {\n if (options?.signal?.aborted) return () => undefined;\n const observer = (event: AuthEvent): void => handler(event);\n const unsubscribe = (): void => {\n tappers.delete(observer);\n options?.signal?.removeEventListener('abort', unsubscribe);\n };\n tappers.add(observer);\n options?.signal?.addEventListener('abort', unsubscribe, { once: true });\n return unsubscribe;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker.js","names":["ROUTES","ROUTE_METHODS","ROUTE_LABELS","id","request","TOKEN_BYTES","LINK_TOKEN","DEVICE_CHALLENGE","DEVICE_APPROVAL","SUBJECT_MAX","CLOCK_TOLERANCE_SECONDS","AuthOperationError","origin","config","allowPublicOrigin","forbidden","cors","ACCOUNT_HEADER","PUBLISHER_HEADER","REQUEST_MARKER_HEADER","methods","maxAge","operator","OPERATOR_SESSION_MAX_AGE_SECONDS","SESSION_MAX_AGE_SECONDS","SignJWT","ARCHIVE_OPERATOR_ROLE","subject","token","sessionToken","unauthenticated","jwtVerify","payload","isWellFormedText","version","account","readAccount","env","error","errors","name","notice","LINK_TTL_MS","prompt","device","escapeHtml","link","body","readJsonBody","badRequest","keys","requestedEmail","challenge","email","isWellFormedEmail","nowMs","admissionRetry","spendSignInBudget","ApiError","user","ensureUser","retryAfterSeconds","spendLinkBudget","tokenHash","sha256Hex","insertToken","approval","path","deleteToken","json","noStore","queryParams","verified","confirmationPage","approveDevice","authenticated","consumeToken","session","sessionCookieValue","row","text","response","parsed","envelope","archiveErrorResponseSchema","retryAfter","uuidSchema","headers","SESSION_COOKIE_NAME","publisher","UUID_RE","archived","serviceRequest","deleteAccount","expiredSessionCookie","revokeSessions","sessionVersion","reason","dbSchemaVersion","authConfig","authReadiness","archiveAuthHealthSchema","method","routeId","next","key","value","tappers","handler","event","requestId","started","failure","toErrorResponse","code","forwardedCode","health","removed","sweep","options"],"sources":["../../src/auth/worker.ts"],"sourcesContent":["import { errors, jwtVerify, SignJWT } from 'jose';\nimport { sha256Hex } from '../_crypto';\nimport type { ArchiveExecutionContext } from '../_server/execution';\nimport {\n ApiError,\n badRequest,\n expiredSessionCookie,\n forbidden,\n json,\n noStore,\n queryParams,\n readJsonBody,\n sessionToken,\n toErrorResponse,\n unauthenticated,\n} from '../_server/http';\nimport { serviceRequest } from '../_server/service';\nimport { UUID_RE } from '../_server/uuid';\nimport {\n ACCOUNT_HEADER,\n ARCHIVE_OPERATOR_ROLE,\n type ArchiveErrorCode,\n AUTH_SCHEMA_VERSION,\n archiveAuthHealthSchema,\n archiveErrorResponseSchema,\n isWellFormedText,\n OPERATOR_SESSION_MAX_AGE_SECONDS,\n PUBLISHER_HEADER,\n REQUEST_MARKER_HEADER,\n SESSION_COOKIE_NAME,\n SESSION_MAX_AGE_SECONDS,\n sessionCookieValue,\n uuidSchema,\n} from '../contract';\nimport { confirmationPage, escapeHtml } from './_confirmation';\nimport { type AuthConfig, type AuthWorkerEnv, authConfig, isWellFormedEmail } from './config';\nimport { type AuthDiagnostic, AuthOperationError } from './errors';\nimport {\n approveDevice,\n authReadiness,\n consumeToken,\n deleteAccount,\n deleteToken,\n ensureUser,\n insertToken,\n LINK_TTL_MS,\n readAccount,\n revokeSessions,\n spendLinkBudget,\n spendSignInBudget,\n sweep,\n} from './store';\n\n/**\n * The reference identity issuer: passwordless email accounts. A POST with an email\n * address ensures the account row and sends a single-use magic link; consuming it\n * signs the browser in by setting the `archive_session` cookie a catalog Worker\n * verifies — an RS256 JWT minted by the active private key, verifiable through\n * `/jwks.json` alongside any configured retired public keys. `OPERATOR_EMAILS` is\n * the one operator-membership truth; operator sessions expire after one hour,\n * bounding authority from previously minted role claims. The account routes export the issuer's\n * own record and erase it together with the catalog profile.\n */\nexport interface AuthWorkerHandler<E extends AuthWorkerEnv = AuthWorkerEnv> {\n fetch(request: Request, env: E, ctx?: ArchiveExecutionContext): Promise<Response>;\n /** The cron sweep: expired links, closed send windows and never-verified accounts. */\n scheduled(controller: unknown, env: E, ctx?: ArchiveExecutionContext): Promise<void>;\n tap(handler: (event: AuthEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;\n}\n\nexport type AuthEvent =\n | {\n readonly type: 'request';\n readonly requestId: string;\n readonly route:\n | '/auth/account'\n | '/auth/jwks.json'\n | '/auth/sign-in'\n | '/auth/device/sign-in'\n | '/auth/device/approve'\n | '/auth/device/claim'\n | '/auth/verify'\n | '/auth/session'\n | '/auth/sign-out-all'\n | '/auth/health'\n | 'unmatched';\n readonly status: number;\n readonly code: ArchiveErrorCode | 'ok';\n readonly ms: number;\n readonly diagnostic?: AuthDiagnostic;\n }\n | { readonly type: 'scheduled'; readonly status: 'ok'; readonly removed: number }\n | { readonly type: 'scheduled'; readonly status: 'failed'; readonly diagnostic: AuthDiagnostic };\n\nconst ROUTES = {\n account: /^\\/api\\/v1\\/auth\\/account$/,\n deviceApprove: /^\\/api\\/v1\\/auth\\/device\\/approve$/,\n deviceClaim: /^\\/api\\/v1\\/auth\\/device\\/claim$/,\n deviceSignIn: /^\\/api\\/v1\\/auth\\/device\\/sign-in$/,\n health: /^\\/api\\/v1\\/auth\\/health$/,\n jwks: /^\\/api\\/v1\\/auth\\/jwks\\.json$/,\n session: /^\\/api\\/v1\\/auth\\/session$/,\n signIn: /^\\/api\\/v1\\/auth\\/sign-in$/,\n signOutAll: /^\\/api\\/v1\\/auth\\/sign-out-all$/,\n verify: /^\\/api\\/v1\\/auth\\/verify$/,\n} as const;\n\nconst ROUTE_METHODS: Record<keyof typeof ROUTES, readonly string[]> = {\n account: ['GET', 'DELETE'],\n deviceApprove: ['GET', 'POST'],\n deviceClaim: ['POST'],\n deviceSignIn: ['POST'],\n health: ['GET'],\n jwks: ['GET'],\n session: ['GET'],\n signIn: ['POST'],\n signOutAll: ['POST'],\n verify: ['GET', 'POST'],\n};\n\nconst ROUTE_LABELS = {\n account: '/auth/account',\n deviceApprove: '/auth/device/approve',\n deviceClaim: '/auth/device/claim',\n deviceSignIn: '/auth/device/sign-in',\n health: '/auth/health',\n jwks: '/auth/jwks.json',\n session: '/auth/session',\n signIn: '/auth/sign-in',\n signOutAll: '/auth/sign-out-all',\n verify: '/auth/verify',\n} as const;\n\nconst matchRoute = (request: Request): keyof typeof ROUTES | undefined =>\n (Object.keys(ROUTES) as (keyof typeof ROUTES)[]).find((id) => ROUTES[id].test(new URL(request.url).pathname));\n\nconst TOKEN_BYTES = 32;\n// Base64url of TOKEN_BYTES random bytes, unpadded.\nconst LINK_TOKEN = /^[A-Za-z0-9_-]{43}$/;\nconst DEVICE_CHALLENGE = /^[a-f0-9]{64}$/;\nconst DEVICE_APPROVAL = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst SUBJECT_MAX = 256;\nconst CLOCK_TOLERANCE_SECONDS = 5;\n\nconst randomToken = (): string =>\n btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(TOKEN_BYTES))))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replace(/=+$/, '');\n\nconst unavailable = (): ApiError => new AuthOperationError(503, 'catalog');\n\n/** Exact allowed Origin plus the request marker, like the catalog's mutation guard. */\nfunction requireMutationGuard(request: Request, config: AuthConfig, allowPublicOrigin = false): string {\n const origin = request.headers.get('origin');\n if (origin === null || (!config.allowedOrigins.has(origin) && !(allowPublicOrigin && origin === config.publicOrigin)))\n throw forbidden();\n if (request.headers.get(REQUEST_MARKER_HEADER) !== '1') throw forbidden();\n return origin;\n}\n\nfunction corsHeaders(request: Request, config: AuthConfig): Record<string, string> {\n const origin = request.headers.get('origin');\n if (\n origin === null ||\n (!config.allowedOrigins.has(origin) &&\n !(['verify', 'deviceApprove'].includes(matchRoute(request) ?? '') && origin === config.publicOrigin))\n )\n return {};\n return { 'access-control-allow-credentials': 'true', 'access-control-allow-origin': origin, vary: 'Origin' };\n}\n\nfunction preflight(request: Request, config: AuthConfig, methods: readonly string[]): Response {\n const cors = corsHeaders(request, config);\n if (Object.keys(cors).length === 0) throw forbidden();\n return new Response(null, {\n headers: {\n ...cors,\n 'access-control-allow-headers': `Content-Type, ${ACCOUNT_HEADER}, ${PUBLISHER_HEADER}, ${REQUEST_MARKER_HEADER}`,\n 'access-control-allow-methods': [...methods, 'OPTIONS'].join(', '),\n 'access-control-max-age': '600',\n },\n status: 204,\n });\n}\n\nconst secure = (config: AuthConfig): boolean => config.appEnv === 'production';\n\nasync function mintSession(\n config: AuthConfig,\n subject: string,\n operator: boolean,\n sessionVersion: number,\n): Promise<{ maxAge: number; token: string }> {\n const maxAge = operator ? OPERATOR_SESSION_MAX_AGE_SECONDS : SESSION_MAX_AGE_SECONDS;\n const token = await new SignJWT({ sessionVersion, ...(operator ? { roles: [ARCHIVE_OPERATOR_ROLE] } : {}) })\n .setProtectedHeader({ alg: 'RS256', kid: config.signing.kid, typ: 'JWT' })\n .setIssuedAt()\n .setJti(crypto.randomUUID())\n .setIssuer(config.issuer)\n .setAudience(config.audience)\n .setSubject(subject)\n .setExpirationTime(`${maxAge}s`)\n .sign(config.signing.key);\n return { maxAge, token };\n}\n\n/** Verifies the request's own session cookie against every published key; returns the token and subject. */\nasync function requireSession(\n request: Request,\n config: AuthConfig,\n env: AuthWorkerEnv,\n): Promise<{ subject: string; token: string; sessionVersion: number }> {\n const token = sessionToken(request);\n if (token === null) throw unauthenticated();\n try {\n const { payload } = await jwtVerify(token, config.verifier, {\n algorithms: ['RS256'],\n audience: config.audience,\n clockTolerance: CLOCK_TOLERANCE_SECONDS,\n issuer: config.issuer,\n maxTokenAge: SESSION_MAX_AGE_SECONDS,\n requiredClaims: ['sub', 'iss', 'aud', 'iat', 'exp', 'sessionVersion'],\n });\n const subject = payload.sub ?? '';\n if (subject.length === 0 || subject.length > SUBJECT_MAX || !isWellFormedText(subject)) throw unauthenticated();\n const version = payload.sessionVersion;\n if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) throw unauthenticated();\n if (typeof payload.iat !== 'number' || payload.iat > Date.now() / 1000 + CLOCK_TOLERANCE_SECONDS)\n throw unauthenticated();\n if (\n Array.isArray(payload.roles) &&\n payload.roles.includes(ARCHIVE_OPERATOR_ROLE) &&\n (typeof payload.exp !== 'number' ||\n payload.exp - payload.iat > OPERATOR_SESSION_MAX_AGE_SECONDS ||\n Date.now() / 1000 - payload.iat > OPERATOR_SESSION_MAX_AGE_SECONDS + CLOCK_TOLERANCE_SECONDS)\n )\n throw unauthenticated();\n const account = await readAccount(env.AUTH_DB, subject);\n if (account === null || account.session_version !== version) throw unauthenticated();\n return { sessionVersion: version, subject, token };\n } catch (error) {\n if (error instanceof errors.JOSEError) throw unauthenticated();\n throw error;\n }\n}\n\nasync function sendLink(\n env: AuthWorkerEnv,\n config: AuthConfig,\n to: string,\n link: string,\n device = false,\n): Promise<void> {\n const name = config.appName;\n const minutes = LINK_TTL_MS / 60_000;\n const notice = `The link works once and expires in ${minutes} minutes. If you did not request it, ignore this email.`;\n const prompt = device ? `Approve sign-in to ${name} on your app:` : `Open this link to sign in to ${name}:`;\n await env.AUTH_EMAIL.send({\n from: config.senderAddress,\n html: `<p>${escapeHtml(prompt)}</p><p><a href=\"${escapeHtml(link)}\">${escapeHtml(link)}</a></p><p>${notice}</p>`,\n subject: `Your ${name} sign-in link`,\n text: `${prompt}\\n\\n${link}\\n\\n${notice}`,\n to,\n });\n}\n\nasync function handleSignIn(\n request: Request,\n env: AuthWorkerEnv,\n config: AuthConfig,\n device = false,\n): Promise<Response> {\n requireMutationGuard(request, config);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body)) throw badRequest();\n const keys = Object.keys(body);\n const requestedEmail: unknown = Reflect.get(body, 'email');\n const challenge: unknown = Reflect.get(body, 'challenge');\n if (\n typeof requestedEmail !== 'string' ||\n (device\n ? keys.length !== 2 || typeof challenge !== 'string' || !DEVICE_CHALLENGE.test(challenge)\n : keys.length !== 1 || keys[0] !== 'email')\n ) {\n throw badRequest();\n }\n const email = requestedEmail.trim().toLowerCase();\n if (!isWellFormedEmail(email)) {\n throw badRequest();\n }\n const nowMs = Date.now();\n const admissionRetry = await spendSignInBudget(env.AUTH_DB, nowMs);\n if (admissionRetry !== null) {\n throw new ApiError(429, 'rate-limited', 'Too many sign-in requests. Try again later.', {\n retryAfterSeconds: admissionRetry,\n });\n }\n const user = await ensureUser(env.AUTH_DB, email);\n const retryAfterSeconds = await spendLinkBudget(env.AUTH_DB, user.id, nowMs);\n if (retryAfterSeconds !== null) {\n throw new ApiError(429, 'rate-limited', 'Too many sign-in emails. Try again later.', { retryAfterSeconds });\n }\n const token = randomToken();\n const tokenHash = await sha256Hex(token);\n await insertToken(env.AUTH_DB, tokenHash, user.id, nowMs);\n try {\n const approval = device\n ? await new SignJWT({ challenge, token })\n .setProtectedHeader({ alg: 'RS256', kid: config.signing.kid, typ: 'archive-device-approval' })\n .setIssuedAt()\n .setIssuer(config.issuer)\n .setAudience(config.audience)\n .setExpirationTime(`${LINK_TTL_MS / 1000}s`)\n .sign(config.signing.key)\n : token;\n const path = device ? 'device/approve' : 'verify';\n await sendLink(env, config, user.email, `${config.publicOrigin}/api/v1/auth/${path}?token=${approval}`, device);\n } catch (error) {\n // An unsent link is useless; the spent allowance is deliberately not refunded.\n await deleteToken(env.AUTH_DB, tokenHash).catch(() => undefined);\n throw new AuthOperationError(503, 'email', { cause: error });\n }\n return json(202, { ok: true }, noStore);\n}\n\nasync function handleDeviceApprove(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n let token: unknown;\n if (request.method === 'GET') {\n token = queryParams(new URL(request.url), ['token']).get('token') ?? '';\n } else {\n requireMutationGuard(request, config, true);\n queryParams(new URL(request.url), []);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).length !== 1)\n throw badRequest('The link is not valid.');\n token = Reflect.get(body, 'token');\n }\n if (typeof token !== 'string' || token.length > 2048 || !DEVICE_APPROVAL.test(token))\n throw badRequest('The link is not valid.');\n let link: unknown;\n let challenge: unknown;\n try {\n const verified = await jwtVerify(token, config.verifier, {\n algorithms: ['RS256'],\n audience: config.audience,\n issuer: config.issuer,\n maxTokenAge: `${LINK_TTL_MS / 1000}s`,\n requiredClaims: ['iss', 'aud', 'iat', 'exp'],\n typ: 'archive-device-approval',\n });\n link = verified.payload.token;\n challenge = verified.payload.challenge;\n } catch (error) {\n if (error instanceof errors.JOSEError)\n throw new ApiError(410, 'gone', 'That link has expired or was already used.');\n throw error;\n }\n if (\n typeof link !== 'string' ||\n !LINK_TOKEN.test(link) ||\n typeof challenge !== 'string' ||\n !DEVICE_CHALLENGE.test(challenge)\n )\n throw badRequest('The link is not valid.');\n if (request.method === 'GET')\n return confirmationPage({\n ...config,\n device: true,\n deviceCode: challenge.slice(0, 8).toUpperCase(),\n nonce: randomToken(),\n token,\n });\n if (!(await approveDevice(env.AUTH_DB, await sha256Hex(link), await sha256Hex(`device:${challenge}`), Date.now())))\n throw new ApiError(410, 'gone', 'That link has expired or was already used.');\n return new Response(null, { headers: noStore, status: 204 });\n}\n\nasync function handleDeviceClaim(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n requireMutationGuard(request, config);\n queryParams(new URL(request.url), []);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).length !== 1)\n throw badRequest();\n const token: unknown = Reflect.get(body, 'token');\n if (typeof token !== 'string' || !LINK_TOKEN.test(token)) throw badRequest();\n if (sessionToken(request) !== null) {\n let authenticated = false;\n try {\n await requireSession(request, config, env);\n authenticated = true;\n } catch (error) {\n if (!(error instanceof ApiError && error.status === 401)) throw error;\n }\n if (authenticated) throw new ApiError(409, 'account-changed', 'Sign out before claiming a device sign-in.');\n }\n const user = await consumeToken(env.AUTH_DB, await sha256Hex(`device:${await sha256Hex(token)}`), Date.now());\n if (user === null) return json(202, { ok: false }, noStore);\n const session = await mintSession(config, user.id, config.operatorEmails.has(user.email), user.sessionVersion);\n return new Response(null, {\n headers: {\n ...noStore,\n 'set-cookie': sessionCookieValue(session.token, { maxAgeSeconds: session.maxAge, secure: secure(config) }),\n },\n status: 204,\n });\n}\n\nasync function handleVerify(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n if (request.method === 'GET') {\n const token = queryParams(new URL(request.url), ['token']).get('token') ?? '';\n if (!LINK_TOKEN.test(token)) throw badRequest('The link is not valid.');\n return confirmationPage({ ...config, nonce: randomToken(), token });\n }\n requireMutationGuard(request, config, true);\n queryParams(new URL(request.url), []);\n const body = await readJsonBody(request);\n if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).length !== 1) {\n throw badRequest('The link is not valid.');\n }\n const token: unknown = Reflect.get(body, 'token');\n if (typeof token !== 'string') throw badRequest('The link is not valid.');\n if (!LINK_TOKEN.test(token)) throw badRequest('The link is not valid.');\n const user = await consumeToken(env.AUTH_DB, await sha256Hex(token), Date.now());\n if (user === null) throw new ApiError(410, 'gone', 'That link has expired or was already used.');\n const session = await mintSession(config, user.id, config.operatorEmails.has(user.email), user.sessionVersion);\n return new Response(null, {\n headers: {\n ...noStore,\n 'set-cookie': sessionCookieValue(session.token, { maxAgeSeconds: session.maxAge, secure: secure(config) }),\n },\n status: 204,\n });\n}\n\n/** The issuer's half of a complete export: the account record only, never tokens or budget state. */\nasync function handleExport(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n const { subject } = await requireSession(request, config, env);\n const row = await readAccount(env.AUTH_DB, subject);\n if (row === null) throw unauthenticated();\n return json(\n 200,\n { createdAt: row.created_at, email: row.email, id: row.id, lastSignInAt: row.last_sign_in_at },\n noStore,\n );\n}\n\n/** Re-serializes a failed catalog response into the public envelope; anything unrecognizable is 503. */\nasync function archiveFailure(response: Response): Promise<Response> {\n const text = await response.text().catch(() => '');\n let parsed: unknown = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = null;\n }\n const envelope = archiveErrorResponseSchema.safeParse(parsed);\n if (!envelope.success || response.status < 400 || response.status > 599) throw unavailable();\n const { code, message, requestId } = envelope.data.error;\n const retryAfter = response.headers.get('retry-after');\n return json(\n response.status,\n { error: { code, message, requestId } },\n retryAfter !== null && /^\\d{1,6}$/.test(retryAfter) ? { ...noStore, 'retry-after': retryAfter } : noStore,\n );\n}\n\n/**\n * Full erasure: catalog session revocation and profile erasure first (through the AUTH_ARCHIVE service binding,\n * presenting the caller's own session), then the issuer's account row, and only then\n * the cookie is expired. Any failure keeps the browser session so the same request\n * can be retried; both halves are idempotent, so a retry after a partial success\n * re-runs the catalog no-op and finishes the issuer side. The account precondition\n * pins every attempt to the confirmed issuer subject, even without a publisher.\n */\nasync function handleErase(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n const origin = requireMutationGuard(request, config);\n const { subject, token } = await requireSession(request, config, env);\n const account = request.headers.get(ACCOUNT_HEADER);\n if (account === null || !uuidSchema.safeParse(account).success) throw badRequest();\n if (account !== subject) {\n throw new ApiError(409, 'account-changed', 'The signed-in account changed since this request was prepared.');\n }\n const headers = new Headers({\n cookie: `${SESSION_COOKIE_NAME}=${token}`,\n origin,\n [REQUEST_MARKER_HEADER]: '1',\n });\n const publisher = request.headers.get(PUBLISHER_HEADER);\n if (publisher !== null) {\n if (!UUID_RE.test(publisher)) throw badRequest();\n headers.set(PUBLISHER_HEADER, publisher);\n }\n let archived: Response;\n try {\n archived = await serviceRequest(\n env.AUTH_ARCHIVE,\n new Request(`${config.publicOrigin}/api/v1/me/account`, { headers, method: 'DELETE', redirect: 'manual' }),\n async (response) => {\n if (response.status === 204) {\n await response.body?.cancel();\n return response;\n }\n return archiveFailure(response);\n },\n );\n } catch (error) {\n throw new AuthOperationError(503, 'catalog', { cause: error });\n }\n if (archived.status !== 204) return archived;\n await deleteAccount(env.AUTH_DB, subject);\n return new Response(null, {\n headers: { ...noStore, 'set-cookie': expiredSessionCookie(secure(config)) },\n status: 204,\n });\n}\n\nasync function handleSignOutAll(request: Request, env: AuthWorkerEnv, config: AuthConfig): Promise<Response> {\n requireMutationGuard(request, config);\n const { subject, sessionVersion } = await requireSession(request, config, env);\n const account = request.headers.get(ACCOUNT_HEADER);\n if (account === null || !uuidSchema.safeParse(account).success) throw badRequest();\n if (account !== subject)\n throw new ApiError(409, 'account-changed', 'The signed-in account changed since this request was prepared.');\n if ((await revokeSessions(env.AUTH_DB, subject, sessionVersion)) === null) throw unauthenticated();\n return json(200, { ok: true }, { ...noStore, 'set-cookie': expiredSessionCookie(secure(config)) });\n}\n\nasync function handleHealth(env: AuthWorkerEnv): Promise<Response> {\n let reason: 'configuration-unavailable' | 'database-unavailable' | 'schema-mismatch' | null = null;\n let dbSchemaVersion: number | null = null;\n try {\n await authConfig(env);\n } catch {\n reason = 'configuration-unavailable';\n }\n if (reason === null) {\n try {\n dbSchemaVersion = await authReadiness(env.AUTH_DB);\n if (dbSchemaVersion !== AUTH_SCHEMA_VERSION) reason = 'schema-mismatch';\n } catch {\n reason = 'database-unavailable';\n }\n }\n return json(\n reason === null ? 200 : 503,\n archiveAuthHealthSchema.parse({\n dbSchemaVersion,\n reason,\n schemaVersion: AUTH_SCHEMA_VERSION,\n status: reason === null ? 'ok' : 'degraded',\n }),\n noStore,\n );\n}\nasync function route(request: Request, env: AuthWorkerEnv): Promise<Response> {\n const method = request.method.toUpperCase();\n const routeId = matchRoute(request);\n if (routeId === undefined) throw new ApiError(404, 'not-found', 'No such route.');\n if (routeId === 'health' && method === 'GET') return handleHealth(env);\n const config = await authConfig(env);\n const cors = corsHeaders(request, config);\n if (method === 'OPTIONS') return preflight(request, config, ROUTE_METHODS[routeId]);\n if (!ROUTE_METHODS[routeId].includes(method)) {\n throw new ApiError(405, 'method-not-allowed', 'That method is not allowed here.');\n }\n let response: Response;\n switch (routeId) {\n case 'deviceApprove':\n response = await handleDeviceApprove(request, env, config);\n break;\n case 'deviceClaim':\n response = await handleDeviceClaim(request, env, config);\n break;\n case 'deviceSignIn':\n response = await handleSignIn(request, env, config, true);\n break;\n case 'health':\n response = await handleHealth(env);\n break;\n case 'session':\n await requireSession(request, config, env);\n response = json(200, { ok: true }, noStore);\n break;\n case 'signOutAll':\n response = await handleSignOutAll(request, env, config);\n break;\n case 'jwks':\n response = json(200, { keys: config.jwks }, noStore);\n break;\n case 'signIn':\n response = await handleSignIn(request, env, config);\n break;\n case 'verify':\n response = await handleVerify(request, env, config);\n break;\n case 'account':\n response = method === 'GET' ? await handleExport(request, env, config) : await handleErase(request, env, config);\n break;\n }\n return withCors(response, cors);\n}\n\nfunction withCors(response: Response, cors: Record<string, string>): Response {\n if (Object.keys(cors).length === 0) return response;\n const next = new Response(response.body, response);\n for (const [key, value] of Object.entries(cors)) next.headers.set(key, value);\n return next;\n}\n\nconst diagnostic = (error: unknown): AuthDiagnostic =>\n error instanceof AuthOperationError ? error.authDiagnostic : { kind: 'internal' };\n\n/**\n * Builds the reference identity issuer. Route behavior is fixed; every deployment\n * difference is validated configuration — the app name, trusted origins, audience and\n * issuer claims, signing keys, operator roster, the catalog service binding, and\n * where links and redirects land.\n */\nexport function createAuthWorker<E extends AuthWorkerEnv>(): AuthWorkerHandler<E> {\n const tappers = new Set<(event: AuthEvent) => void>();\n const emit = (event: AuthEvent): void => {\n for (const handler of tappers) {\n try {\n handler(event);\n } catch {\n // Observers never change request or scheduled outcomes.\n }\n }\n };\n return {\n async fetch(request: Request, env: E): Promise<Response> {\n const requestId = crypto.randomUUID();\n const started = Date.now();\n let response: Response;\n let failure: unknown;\n try {\n response = await route(request, env);\n } catch (error) {\n failure = error;\n response = toErrorResponse(error, requestId);\n // Failures stay readable by allowed origins; config faults have no trusted origin set.\n const config = await authConfig(env).catch(() => null);\n if (config !== null) {\n if (\n (matchRoute(request) === 'verify' || matchRoute(request) === 'deviceApprove') &&\n request.method === 'GET' &&\n error instanceof ApiError &&\n (error.status === 400 || (matchRoute(request) === 'deviceApprove' && error.status === 410))\n )\n response = confirmationPage({\n ...config,\n device: matchRoute(request) === 'deviceApprove',\n errorStatus: error.status === 410 ? 410 : 400,\n nonce: randomToken(),\n token: null,\n });\n response = withCors(response, corsHeaders(request, config));\n }\n }\n if (matchRoute(request) === 'verify' || matchRoute(request) === 'deviceApprove') {\n response.headers.set('cache-control', 'no-store');\n response.headers.set('referrer-policy', 'no-referrer');\n response.headers.set('x-content-type-options', 'nosniff');\n if (!response.headers.has('content-security-policy')) {\n response.headers.set(\n 'content-security-policy',\n \"default-src 'none'; base-uri 'none'; frame-ancestors 'none'\",\n );\n }\n }\n if (tappers.size > 0) {\n const routeId = matchRoute(request);\n const code =\n response.status < 400\n ? 'ok'\n : routeId === 'health'\n ? 'unavailable'\n : failure instanceof ApiError\n ? failure.code\n : 'internal';\n // Account erasure may forward a catalog error response rather than throw.\n const forwardedCode =\n failure === undefined && response.status >= 400\n ? archiveErrorResponseSchema.safeParse(\n await response\n .clone()\n .json()\n .catch(() => null),\n )\n : null;\n const health =\n routeId === 'health' && response.status === 503\n ? archiveAuthHealthSchema.safeParse(\n await response\n .clone()\n .json()\n .catch(() => null),\n )\n : null;\n emit({\n code: forwardedCode?.success ? forwardedCode.data.error.code : code,\n ms: Math.max(0, Date.now() - started),\n requestId,\n route: routeId === undefined ? 'unmatched' : ROUTE_LABELS[routeId],\n status: response.status,\n type: 'request',\n ...(response.status >= 500\n ? {\n diagnostic: health?.success\n ? {\n kind:\n health.data.reason === 'configuration-unavailable'\n ? 'configuration'\n : health.data.reason === 'schema-mismatch'\n ? 'schema'\n : 'database',\n }\n : failure === undefined\n ? { kind: 'catalog' as const }\n : diagnostic(failure),\n }\n : {}),\n });\n }\n return response;\n },\n\n async scheduled(_controller: unknown, env: E): Promise<void> {\n try {\n await authConfig(env);\n const removed = await sweep(env.AUTH_DB, Date.now());\n if (tappers.size > 0) emit({ removed, status: 'ok', type: 'scheduled' });\n } catch (error) {\n if (tappers.size > 0) emit({ diagnostic: diagnostic(error), status: 'failed', type: 'scheduled' });\n throw error;\n }\n },\n tap(handler, options) {\n if (options?.signal?.aborted) return () => undefined;\n const observer = (event: AuthEvent): void => handler(event);\n const unsubscribe = (): void => {\n tappers.delete(observer);\n options?.signal?.removeEventListener('abort', unsubscribe);\n };\n tappers.add(observer);\n options?.signal?.addEventListener('abort', unsubscribe, { once: true });\n return unsubscribe;\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/archive",
|
|
3
|
-
"version": "26.11.
|
|
3
|
+
"version": "26.11.17",
|
|
4
4
|
"description": "Community content publishing for Cloudflare Workers + D1: a hardened Worker core over per-collection payload schemas",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"test:types": "tsc -p tsconfig.json --noEmit"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@vielzeug/spell": "26.11.
|
|
65
|
+
"@vielzeug/spell": "26.11.17",
|
|
66
66
|
"jose": "6.2.15"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|