@_mustachio/openauth 0.11.0 → 0.12.0

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.
Files changed (164) hide show
  1. package/dist/esm/adapters/d1/index.js +18 -9
  2. package/dist/esm/adapters/d1/migrations.js +7 -2
  3. package/dist/esm/adapters/d1/session-store.js +39 -0
  4. package/dist/esm/adapters/d1/session.js +2 -2
  5. package/dist/esm/adapters/durable-object/index.js +4 -0
  6. package/dist/esm/adapters/durable-object/session-store.js +42 -0
  7. package/dist/esm/adapters/dynamo/index.js +24 -7
  8. package/dist/esm/adapters/dynamo/session-store.js +49 -0
  9. package/dist/esm/adapters/kms/index.js +6 -2
  10. package/dist/esm/adapters/kv/index.js +6 -2
  11. package/dist/esm/adapters/memory/index.js +24 -7
  12. package/dist/esm/adapters/memory/session-store.js +23 -0
  13. package/dist/esm/adapters/postgres/executor.js +2 -2
  14. package/dist/esm/adapters/postgres/index.js +33 -10
  15. package/dist/esm/adapters/postgres/migrations.js +8 -2
  16. package/dist/esm/adapters/postgres/session-store.js +42 -0
  17. package/dist/esm/client.js +27 -27
  18. package/dist/esm/domain/authorize.js +2 -2
  19. package/dist/esm/domain/callback.js +121 -7
  20. package/dist/esm/domain/client-auth.js +2 -2
  21. package/dist/esm/domain/crypto.js +14 -14
  22. package/dist/esm/domain/discovery.js +2 -2
  23. package/dist/esm/domain/dpop.js +4 -4
  24. package/dist/esm/domain/id-token.js +5 -5
  25. package/dist/esm/domain/jwt.js +4 -4
  26. package/dist/esm/domain/method-dispatch.js +27 -1
  27. package/dist/esm/domain/method-route.js +80 -1
  28. package/dist/esm/domain/par.js +2 -2
  29. package/dist/esm/domain/pkce.js +2 -2
  30. package/dist/esm/domain/revoke.js +2 -2
  31. package/dist/esm/domain/state-envelope.js +2 -2
  32. package/dist/esm/domain/token.js +5 -5
  33. package/dist/esm/error.js +8 -8
  34. package/dist/esm/http/cookies.js +19 -2
  35. package/dist/esm/http/errors.js +4 -4
  36. package/dist/esm/http/handlers/authorize.js +3 -19
  37. package/dist/esm/http/handlers/callback.js +7 -1
  38. package/dist/esm/http/handlers/metadata.js +2 -2
  39. package/dist/esm/http/handlers/method-route.js +47 -2
  40. package/dist/esm/http/handlers/revocation.js +2 -2
  41. package/dist/esm/http/middleware/tenant.js +6 -6
  42. package/dist/esm/http/schemas/authorize.js +2 -2
  43. package/dist/esm/http/schemas/revocation.js +2 -2
  44. package/dist/esm/http/schemas/token.js +7 -7
  45. package/dist/esm/index.js +33 -32
  46. package/dist/esm/methods/oauth2-factory.js +2 -2
  47. package/dist/esm/methods/password-hash.js +2 -2
  48. package/dist/esm/methods/providers/index.js +30 -14
  49. package/dist/esm/methods/saml-sp/acs.js +208 -0
  50. package/dist/esm/methods/saml-sp/attributes.js +75 -0
  51. package/dist/esm/methods/saml-sp/authnrequest.js +63 -0
  52. package/dist/esm/methods/saml-sp/cache-provider.js +33 -0
  53. package/dist/esm/methods/saml-sp/cert-rotation.js +13 -0
  54. package/dist/esm/methods/saml-sp/factory.js +70 -0
  55. package/dist/esm/methods/saml-sp/index.js +7 -0
  56. package/dist/esm/methods/saml-sp/metadata.js +69 -0
  57. package/dist/esm/methods/saml-sp/method.js +29 -0
  58. package/dist/esm/methods/saml-sp/parse-idp-metadata.js +102 -0
  59. package/dist/esm/methods/saml-sp/saml-instance.js +49 -0
  60. package/dist/esm/methods/saml-sp/slo-initiate.js +84 -0
  61. package/dist/esm/methods/saml-sp/sls.js +154 -0
  62. package/dist/esm/methods/saml-sp/types.js +0 -0
  63. package/dist/esm/types/result.js +3 -3
  64. package/dist/esm/ui/forms.js +3 -3
  65. package/dist/esm/ui/index.js +8 -5
  66. package/dist/esm/ui/picker.js +2 -2
  67. package/dist/types/adapters/d1/migrations.d.ts.map +1 -1
  68. package/dist/types/adapters/d1/session-store.d.ts +3 -0
  69. package/dist/types/adapters/d1/session-store.d.ts.map +1 -1
  70. package/dist/types/adapters/durable-object/session-store.d.ts +3 -0
  71. package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -1
  72. package/dist/types/adapters/dynamo/session-store.d.ts +3 -0
  73. package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -1
  74. package/dist/types/adapters/memory/session-store.d.ts +3 -0
  75. package/dist/types/adapters/memory/session-store.d.ts.map +1 -1
  76. package/dist/types/adapters/postgres/migrations.d.ts +1 -1
  77. package/dist/types/adapters/postgres/migrations.d.ts.map +1 -1
  78. package/dist/types/adapters/postgres/session-store.d.ts +3 -0
  79. package/dist/types/adapters/postgres/session-store.d.ts.map +1 -1
  80. package/dist/types/domain/callback.d.ts +16 -1
  81. package/dist/types/domain/callback.d.ts.map +1 -1
  82. package/dist/types/domain/method-dispatch.d.ts.map +1 -1
  83. package/dist/types/domain/method-route.d.ts +57 -1
  84. package/dist/types/domain/method-route.d.ts.map +1 -1
  85. package/dist/types/http/context.d.ts +3 -1
  86. package/dist/types/http/context.d.ts.map +1 -1
  87. package/dist/types/http/cookies.d.ts +9 -1
  88. package/dist/types/http/cookies.d.ts.map +1 -1
  89. package/dist/types/http/handlers/callback.d.ts.map +1 -1
  90. package/dist/types/http/handlers/method-route.d.ts.map +1 -1
  91. package/dist/types/index.d.ts +1 -1
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/dist/types/methods/saml-sp/acs.d.ts +4 -0
  94. package/dist/types/methods/saml-sp/acs.d.ts.map +1 -0
  95. package/dist/types/methods/saml-sp/attributes.d.ts +40 -0
  96. package/dist/types/methods/saml-sp/attributes.d.ts.map +1 -0
  97. package/dist/types/methods/saml-sp/authnrequest.d.ts +4 -0
  98. package/dist/types/methods/saml-sp/authnrequest.d.ts.map +1 -0
  99. package/dist/types/methods/saml-sp/cache-provider.d.ts +38 -0
  100. package/dist/types/methods/saml-sp/cache-provider.d.ts.map +1 -0
  101. package/dist/types/methods/saml-sp/cert-rotation.d.ts +23 -0
  102. package/dist/types/methods/saml-sp/cert-rotation.d.ts.map +1 -0
  103. package/dist/types/methods/saml-sp/factory.d.ts +704 -0
  104. package/dist/types/methods/saml-sp/factory.d.ts.map +1 -0
  105. package/dist/types/methods/saml-sp/index.d.ts +24 -0
  106. package/dist/types/methods/saml-sp/index.d.ts.map +1 -0
  107. package/dist/types/methods/saml-sp/metadata.d.ts +42 -0
  108. package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -0
  109. package/dist/types/methods/saml-sp/method.d.ts +17 -0
  110. package/dist/types/methods/saml-sp/method.d.ts.map +1 -0
  111. package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts +4 -0
  112. package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts.map +1 -0
  113. package/dist/types/methods/saml-sp/saml-instance.d.ts +81 -0
  114. package/dist/types/methods/saml-sp/saml-instance.d.ts.map +1 -0
  115. package/dist/types/methods/saml-sp/slo-initiate.d.ts +4 -0
  116. package/dist/types/methods/saml-sp/slo-initiate.d.ts.map +1 -0
  117. package/dist/types/methods/saml-sp/sls.d.ts +4 -0
  118. package/dist/types/methods/saml-sp/sls.d.ts.map +1 -0
  119. package/dist/types/methods/saml-sp/types.d.ts +204 -0
  120. package/dist/types/methods/saml-sp/types.d.ts.map +1 -0
  121. package/dist/types/ports/audit-log.d.ts +18 -5
  122. package/dist/types/ports/audit-log.d.ts.map +1 -1
  123. package/dist/types/ports/session-store.d.ts +23 -0
  124. package/dist/types/ports/session-store.d.ts.map +1 -1
  125. package/dist/types/types/idp.d.ts +71 -0
  126. package/dist/types/types/idp.d.ts.map +1 -1
  127. package/dist/types/types/method.d.ts +109 -0
  128. package/dist/types/types/method.d.ts.map +1 -1
  129. package/package.json +9 -1
  130. package/src/adapters/d1/migrations.ts +5 -0
  131. package/src/adapters/d1/session-store.ts +68 -0
  132. package/src/adapters/durable-object/session-store.ts +61 -0
  133. package/src/adapters/dynamo/session-store.ts +63 -0
  134. package/src/adapters/memory/session-store.ts +39 -0
  135. package/src/adapters/postgres/migrations.ts +6 -0
  136. package/src/adapters/postgres/session-store.ts +69 -0
  137. package/src/domain/callback.ts +215 -7
  138. package/src/domain/method-dispatch.ts +53 -1
  139. package/src/domain/method-route.ts +190 -1
  140. package/src/http/context.ts +6 -0
  141. package/src/http/cookies.ts +19 -1
  142. package/src/http/handlers/authorize.ts +2 -21
  143. package/src/http/handlers/callback.ts +9 -1
  144. package/src/http/handlers/method-route.ts +75 -2
  145. package/src/index.ts +3 -0
  146. package/src/methods/saml-sp/acs.ts +399 -0
  147. package/src/methods/saml-sp/attributes.ts +137 -0
  148. package/src/methods/saml-sp/authnrequest.ts +128 -0
  149. package/src/methods/saml-sp/cache-provider.ts +76 -0
  150. package/src/methods/saml-sp/cert-rotation.ts +34 -0
  151. package/src/methods/saml-sp/factory.ts +118 -0
  152. package/src/methods/saml-sp/index.ts +34 -0
  153. package/src/methods/saml-sp/metadata.ts +204 -0
  154. package/src/methods/saml-sp/method.ts +65 -0
  155. package/src/methods/saml-sp/parse-idp-metadata.ts +164 -0
  156. package/src/methods/saml-sp/saml-instance.ts +173 -0
  157. package/src/methods/saml-sp/slo-initiate.ts +177 -0
  158. package/src/methods/saml-sp/sls.ts +306 -0
  159. package/src/methods/saml-sp/types.ts +203 -0
  160. package/src/ports/CONSISTENCY.md +25 -0
  161. package/src/ports/audit-log.ts +18 -5
  162. package/src/ports/session-store.ts +28 -0
  163. package/src/types/idp.ts +74 -0
  164. package/src/types/method.ts +110 -0
@@ -0,0 +1,69 @@
1
+ // src/methods/saml-sp/metadata.ts
2
+ import { authError } from "../../types/error";
3
+ import { deriveSpEntityId } from "./saml-instance";
4
+ var NAME_ID_FORMAT_URN = {
5
+ persistent: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
6
+ transient: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
7
+ emailAddress: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
8
+ unspecified: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
9
+ };
10
+ var HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
11
+ var HTTP_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
12
+ function xmlEscape(s) {
13
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
14
+ }
15
+ function certBody(pem) {
16
+ return pem.replace(/-----BEGIN CERTIFICATE-----/g, "").replace(/-----END CERTIFICATE-----/g, "").replace(/\s+/g, "");
17
+ }
18
+ function buildSpMetadataXml(input) {
19
+ const entityId = xmlEscape(input.spEntityId);
20
+ const acs = xmlEscape(input.acsUrl);
21
+ const signed = input.signingCertPem !== undefined;
22
+ const nameIdLine = input.nameIdFormat !== undefined ? `
23
+ <md:NameIDFormat>${NAME_ID_FORMAT_URN[input.nameIdFormat]}</md:NameIDFormat>` : "";
24
+ const x509 = (cert) => `<ds:KeyInfo><ds:X509Data><ds:X509Certificate>` + certBody(cert) + `</ds:X509Certificate></ds:X509Data></ds:KeyInfo>`;
25
+ const keyDescriptor = (signed ? `
26
+ <md:KeyDescriptor use="signing">` + x509(input.signingCertPem) + `</md:KeyDescriptor>` : "") + (input.encryptionCertPem !== undefined ? `
27
+ <md:KeyDescriptor use="encryption">` + x509(input.encryptionCertPem) + `</md:KeyDescriptor>` : "");
28
+ const sls = input.slsUrl !== undefined ? `
29
+ <md:SingleLogoutService ` + `Binding="${HTTP_REDIRECT}" Location="${xmlEscape(input.slsUrl)}"/>` + `
30
+ <md:SingleLogoutService ` + `Binding="${HTTP_POST}" Location="${xmlEscape(input.slsUrl)}"/>` : "";
31
+ return `<?xml version="1.0" encoding="UTF-8"?>
32
+ ` + `<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ` + `xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ` + `entityID="${entityId}">
33
+ ` + ` <md:SPSSODescriptor AuthnRequestsSigned="${signed}" ` + `WantAssertionsSigned="true" ` + `protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">` + keyDescriptor + sls + nameIdLine + `
34
+ <md:AssertionConsumerService ` + `Binding="${HTTP_POST}" Location="${acs}" index="0" isDefault="true"/>` + `
35
+ </md:SPSSODescriptor>
36
+ ` + `</md:EntityDescriptor>
37
+ `;
38
+ }
39
+ async function buildSpMetadata(ctx, methodId, config) {
40
+ if (!ctx.dispatch) {
41
+ return {
42
+ kind: "error",
43
+ error: authError.internalError("saml-sp: metadata route dispatched without issuer context " + "(ctx.dispatch is null)")
44
+ };
45
+ }
46
+ const spEntityId = deriveSpEntityId(ctx.dispatch.issuerUrl, ctx.tenant.id, methodId);
47
+ const cb = new URL(ctx.dispatch.callbackUrl);
48
+ const slsUrl = `${cb.protocol}//${cb.host}/m/${methodId}/sls`;
49
+ const xml = buildSpMetadataXml({
50
+ spEntityId,
51
+ acsUrl: ctx.dispatch.callbackUrl,
52
+ ...config.idp.sloUrl ? { slsUrl } : {},
53
+ ...config.idp.nameIdFormat !== undefined ? { nameIdFormat: config.idp.nameIdFormat } : {},
54
+ ...config.signAuthnRequest && config.signingKey ? { signingCertPem: config.signingKey.certPem } : {},
55
+ ...config.allowEncryptedAssertions && config.decryptionKey ? { encryptionCertPem: config.decryptionKey.certPem } : {}
56
+ });
57
+ return {
58
+ kind: "challenge",
59
+ response: new Response(xml, {
60
+ status: 200,
61
+ headers: { "content-type": "application/samlmetadata+xml" }
62
+ }),
63
+ cache: { sMaxAge: 300 }
64
+ };
65
+ }
66
+ export {
67
+ buildSpMetadata,
68
+ buildSpMetadataXml
69
+ };
@@ -0,0 +1,29 @@
1
+ // src/methods/saml-sp/method.ts
2
+ import { consumeAssertion } from "./acs";
3
+ import { buildAuthnRequestRedirect } from "./authnrequest";
4
+ import { buildSpMetadata } from "./metadata";
5
+ import { consumeSls } from "./sls";
6
+ import { initiateSpLogout } from "./slo-initiate";
7
+ function buildSamlSpMethod(id, kind, config) {
8
+ return {
9
+ id,
10
+ kind,
11
+ type: "custom",
12
+ routes: {
13
+ "GET /authorize": (ctx) => buildAuthnRequestRedirect(ctx, id, config),
14
+ "GET /callback": (ctx) => consumeAssertion(ctx, id, config),
15
+ "GET /metadata": (ctx) => buildSpMetadata(ctx, id, config),
16
+ "GET /sls": (ctx) => consumeSls(ctx, id, config),
17
+ "POST /sls": (ctx) => consumeSls(ctx, id, config),
18
+ "POST /logout": (ctx) => initiateSpLogout(ctx, id, config)
19
+ },
20
+ publicRoutes: [
21
+ "GET /metadata",
22
+ ...config.idp.sloUrl ? ["GET /sls", "POST /sls", "POST /logout"] : []
23
+ ],
24
+ unsolicitedCallback: config.idpInitiated !== undefined
25
+ };
26
+ }
27
+ export {
28
+ buildSamlSpMethod
29
+ };
@@ -0,0 +1,102 @@
1
+ // src/methods/saml-sp/parse-idp-metadata.ts
2
+ import { authError } from "../../types/error";
3
+ import { err, ok } from "../../types/result";
4
+ import xmldom from "@xmldom/xmldom";
5
+ var { DOMParser } = xmldom;
6
+ var HTTP_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
7
+ var HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
8
+ var NAME_ID_FORMAT_BY_URN = {
9
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent": "persistent",
10
+ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient": "transient",
11
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress": "emailAddress",
12
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified": "unspecified"
13
+ };
14
+ function els(root, localName) {
15
+ const list = root.getElementsByTagNameNS("*", localName);
16
+ const out = [];
17
+ for (let i = 0;i < list.length; i++) {
18
+ const n = list[i];
19
+ if (n)
20
+ out.push(n);
21
+ }
22
+ return out;
23
+ }
24
+ function pickEndpoint(endpoints) {
25
+ if (endpoints.length === 0)
26
+ return;
27
+ const byBinding = (b) => endpoints.find((e) => e.getAttribute("Binding") === b);
28
+ const chosen = byBinding(HTTP_REDIRECT) ?? byBinding(HTTP_POST) ?? endpoints[0];
29
+ const loc = chosen?.getAttribute("Location");
30
+ return loc && loc.length > 0 ? loc : undefined;
31
+ }
32
+ function toPem(rawX509) {
33
+ const b64 = rawX509.replace(/\s+/g, "");
34
+ const lines = b64.match(/.{1,64}/g) ?? [b64];
35
+ return `-----BEGIN CERTIFICATE-----
36
+ ` + lines.join(`
37
+ `) + `
38
+ -----END CERTIFICATE-----`;
39
+ }
40
+ function parseSamlIdpMetadata(xml) {
41
+ if (typeof xml !== "string" || xml.trim().length === 0) {
42
+ return err(authError.invalidRequest("metadata XML is empty"));
43
+ }
44
+ let doc;
45
+ try {
46
+ doc = new DOMParser().parseFromString(xml, "text/xml");
47
+ } catch (e) {
48
+ return err(authError.invalidRequest(`metadata XML is not well-formed: ${e instanceof Error ? e.message : String(e)}`));
49
+ }
50
+ if (!doc?.documentElement) {
51
+ return err(authError.invalidRequest("metadata XML has no root element"));
52
+ }
53
+ const entityDescriptor = els(doc, "EntityDescriptor")[0];
54
+ if (!entityDescriptor) {
55
+ return err(authError.invalidRequest("metadata has no <EntityDescriptor>"));
56
+ }
57
+ const entityId = entityDescriptor.getAttribute("entityID");
58
+ if (!entityId) {
59
+ return err(authError.invalidRequest("EntityDescriptor has no entityID"));
60
+ }
61
+ const idp = els(entityDescriptor, "IDPSSODescriptor")[0];
62
+ if (!idp) {
63
+ return err(authError.invalidRequest("not an IdP metadata document (no <IDPSSODescriptor>) — this looks " + "like SP metadata"));
64
+ }
65
+ const ssoUrl = pickEndpoint(els(idp, "SingleSignOnService"));
66
+ if (!ssoUrl) {
67
+ return err(authError.invalidRequest("IDPSSODescriptor has no usable <SingleSignOnService> Location"));
68
+ }
69
+ const sloUrl = pickEndpoint(els(idp, "SingleLogoutService"));
70
+ let nameIdFormat;
71
+ for (const f of els(idp, "NameIDFormat")) {
72
+ const mapped = NAME_ID_FORMAT_BY_URN[(f.textContent ?? "").trim()];
73
+ if (mapped) {
74
+ nameIdFormat = mapped;
75
+ break;
76
+ }
77
+ }
78
+ const signingCerts = [];
79
+ for (const kd of els(idp, "KeyDescriptor")) {
80
+ const use = kd.getAttribute("use");
81
+ if (use && use !== "signing")
82
+ continue;
83
+ for (const cert of els(kd, "X509Certificate")) {
84
+ const body = (cert.textContent ?? "").trim();
85
+ if (body.length > 0)
86
+ signingCerts.push({ pem: toPem(body) });
87
+ }
88
+ }
89
+ if (signingCerts.length === 0) {
90
+ return err(authError.invalidRequest("IDPSSODescriptor has no signing <X509Certificate>"));
91
+ }
92
+ return ok({
93
+ entityId,
94
+ ssoUrl,
95
+ ...sloUrl !== undefined ? { sloUrl } : {},
96
+ ...nameIdFormat !== undefined ? { nameIdFormat } : {},
97
+ signingCerts
98
+ });
99
+ }
100
+ export {
101
+ parseSamlIdpMetadata
102
+ };
@@ -0,0 +1,49 @@
1
+ // src/methods/saml-sp/saml-instance.ts
2
+ import nodeSaml from "@node-saml/node-saml";
3
+ import { methodScratchCacheProvider } from "./cache-provider";
4
+ import { selectActiveCertPems } from "./cert-rotation";
5
+ var { SAML, ValidateInResponseTo } = nodeSaml;
6
+ var NAME_ID_FORMAT_URN = {
7
+ persistent: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
8
+ transient: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
9
+ emailAddress: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
10
+ unspecified: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
11
+ };
12
+ var IN_RESPONSE_TO_TTL_MS = 60 * 60 * 1000;
13
+ function deriveSpEntityId(issuerUrl, tenantId, methodId) {
14
+ const base = issuerUrl.endsWith("/") ? issuerUrl.slice(0, -1) : issuerUrl;
15
+ return `${base}/${tenantId}/${methodId}`;
16
+ }
17
+ function buildSamlInstance(config, binding, nowMs) {
18
+ const activeCerts = selectActiveCertPems(config.idp.signingCerts, nowMs);
19
+ if (activeCerts.length === 0) {
20
+ throw new Error("saml-sp: no IdP signing certificate is currently within its " + "validity window — check SamlSpConfig.idp.signingCerts notBefore/notAfter.");
21
+ }
22
+ const identifierFormat = config.idp.nameIdFormat !== undefined ? NAME_ID_FORMAT_URN[config.idp.nameIdFormat] : null;
23
+ return new SAML({
24
+ idpCert: activeCerts,
25
+ issuer: binding.spEntityId,
26
+ callbackUrl: binding.acsUrl,
27
+ entryPoint: config.idp.ssoUrl,
28
+ idpIssuer: config.idp.entityId,
29
+ audience: binding.spEntityId,
30
+ identifierFormat,
31
+ validateInResponseTo: binding.logout ? ValidateInResponseTo.never : binding.idpInitiated ? ValidateInResponseTo.ifPresent : ValidateInResponseTo.always,
32
+ wantAssertionsSigned: true,
33
+ wantAuthnResponseSigned: false,
34
+ acceptedClockSkewMs: (config.clockSkewSeconds ?? 60) * 1000,
35
+ cacheProvider: methodScratchCacheProvider(binding.scratch, IN_RESPONSE_TO_TTL_MS),
36
+ ...binding.signing ? {
37
+ privateKey: binding.signing.privateKeyPem,
38
+ publicCert: binding.signing.certPem,
39
+ signatureAlgorithm: "sha256"
40
+ } : {},
41
+ ...binding.logoutUrl ? { logoutUrl: binding.logoutUrl } : {},
42
+ ...binding.decryptionPvk ? { decryptionPvk: binding.decryptionPvk } : {}
43
+ });
44
+ }
45
+ export {
46
+ NAME_ID_FORMAT_URN,
47
+ buildSamlInstance,
48
+ deriveSpEntityId
49
+ };
@@ -0,0 +1,84 @@
1
+ // src/methods/saml-sp/slo-initiate.ts
2
+ import { authError } from "../../types/error";
3
+ import {
4
+ buildSamlInstance,
5
+ deriveSpEntityId,
6
+ NAME_ID_FORMAT_URN
7
+ } from "./saml-instance";
8
+ async function initiateSpLogout(ctx, methodId, config) {
9
+ if (!ctx.dispatch) {
10
+ return {
11
+ kind: "error",
12
+ error: authError.internalError("saml-sp: /logout dispatched without issuer context (ctx.dispatch is null)")
13
+ };
14
+ }
15
+ if (!config.idp.sloUrl) {
16
+ return {
17
+ kind: "error",
18
+ error: authError.internalError("saml-sp: /logout reached with no idp.sloUrl configured")
19
+ };
20
+ }
21
+ let nameId;
22
+ let sessionIndex;
23
+ let relayState;
24
+ let nameIdFormatParam;
25
+ try {
26
+ const form = new URLSearchParams(await ctx.request.text());
27
+ nameId = form.get("nameId");
28
+ sessionIndex = form.get("sessionIndex");
29
+ relayState = form.get("relayState");
30
+ nameIdFormatParam = form.get("nameIdFormat");
31
+ } catch {
32
+ return {
33
+ kind: "error",
34
+ error: authError.internalError("saml-sp: /logout could not read POST body")
35
+ };
36
+ }
37
+ if (!nameId) {
38
+ return { kind: "denied", reason: "missing nameId" };
39
+ }
40
+ if (nameIdFormatParam !== null && !(nameIdFormatParam in NAME_ID_FORMAT_URN)) {
41
+ return {
42
+ kind: "denied",
43
+ reason: `unrecognized nameIdFormat "${nameIdFormatParam}" — expected one ` + `of: ${Object.keys(NAME_ID_FORMAT_URN).join(", ")}`
44
+ };
45
+ }
46
+ const nameIDFormat = nameIdFormatParam !== null ? NAME_ID_FORMAT_URN[nameIdFormatParam] : config.idp.nameIdFormat !== undefined ? NAME_ID_FORMAT_URN[config.idp.nameIdFormat] : NAME_ID_FORMAT_URN.persistent;
47
+ const spEntityId = deriveSpEntityId(ctx.dispatch.issuerUrl, ctx.tenant.id, methodId);
48
+ let redirectUrl;
49
+ try {
50
+ const saml = buildSamlInstance(config, {
51
+ spEntityId,
52
+ acsUrl: ctx.dispatch.callbackUrl,
53
+ scratch: ctx.methodScratch,
54
+ logoutUrl: config.idp.sloUrl,
55
+ logout: true,
56
+ ...config.signingKey ? {
57
+ signing: {
58
+ privateKeyPem: config.signingKey.privateKeyPem,
59
+ certPem: config.signingKey.certPem
60
+ }
61
+ } : {}
62
+ }, Date.now());
63
+ redirectUrl = await saml.getLogoutUrlAsync({
64
+ nameID: nameId,
65
+ nameIDFormat,
66
+ ...sessionIndex !== null ? { sessionIndex } : {}
67
+ }, relayState ?? "", {});
68
+ } catch (e) {
69
+ return {
70
+ kind: "error",
71
+ error: authError.internalError(`saml-sp: failed to build LogoutRequest: ${e instanceof Error ? e.message : String(e)}`, e)
72
+ };
73
+ }
74
+ return {
75
+ kind: "challenge",
76
+ response: new Response(null, {
77
+ status: 302,
78
+ headers: { location: redirectUrl }
79
+ })
80
+ };
81
+ }
82
+ export {
83
+ initiateSpLogout
84
+ };
@@ -0,0 +1,154 @@
1
+ // src/methods/saml-sp/sls.ts
2
+ import { authError } from "../../types/error";
3
+ import { isErr } from "../../types/result";
4
+ import { buildSamlInstance, deriveSpEntityId } from "./saml-instance";
5
+ var SLO_REPLAY_HORIZON_MS = 10 * 60000;
6
+ async function consumeSls(ctx, methodId, config) {
7
+ if (!ctx.dispatch) {
8
+ return {
9
+ kind: "error",
10
+ error: authError.internalError("saml-sp: /sls dispatched without issuer context (ctx.dispatch is null)")
11
+ };
12
+ }
13
+ if (!config.idp.sloUrl) {
14
+ return {
15
+ kind: "error",
16
+ error: authError.internalError("saml-sp: /sls reached with no idp.sloUrl configured")
17
+ };
18
+ }
19
+ const spEntityId = deriveSpEntityId(ctx.dispatch.issuerUrl, ctx.tenant.id, methodId);
20
+ const url = new URL(ctx.request.url);
21
+ const originalQuery = url.search.replace(/^\?/, "");
22
+ let samlRequest;
23
+ let samlResponse;
24
+ let relayState;
25
+ const isPost = ctx.request.method.toUpperCase() === "POST";
26
+ let postForm = null;
27
+ try {
28
+ if (isPost) {
29
+ postForm = new URLSearchParams(await ctx.request.text());
30
+ samlRequest = postForm.get("SAMLRequest");
31
+ samlResponse = postForm.get("SAMLResponse");
32
+ relayState = postForm.get("RelayState");
33
+ } else {
34
+ samlRequest = url.searchParams.get("SAMLRequest");
35
+ samlResponse = url.searchParams.get("SAMLResponse");
36
+ relayState = url.searchParams.get("RelayState");
37
+ }
38
+ } catch {
39
+ return {
40
+ kind: "error",
41
+ error: authError.internalError("saml-sp: /sls could not read request")
42
+ };
43
+ }
44
+ if (!samlRequest && !samlResponse) {
45
+ return { kind: "denied", reason: "missing SAMLRequest / SAMLResponse" };
46
+ }
47
+ let saml;
48
+ try {
49
+ saml = buildSamlInstance(config, {
50
+ spEntityId,
51
+ acsUrl: ctx.dispatch.callbackUrl,
52
+ scratch: ctx.methodScratch,
53
+ logoutUrl: config.idp.sloUrl,
54
+ logout: true,
55
+ ...config.signingKey ? {
56
+ signing: {
57
+ privateKeyPem: config.signingKey.privateKeyPem,
58
+ certPem: config.signingKey.certPem
59
+ }
60
+ } : {}
61
+ }, Date.now());
62
+ } catch (e) {
63
+ return {
64
+ kind: "error",
65
+ error: authError.internalError(`saml-sp: cannot construct logout verifier: ${e instanceof Error ? e.message : String(e)}`, e)
66
+ };
67
+ }
68
+ if (samlRequest) {
69
+ let profile;
70
+ try {
71
+ const res = isPost ? await saml.validatePostRequestAsync({
72
+ SAMLRequest: samlRequest,
73
+ ...relayState !== null ? { RelayState: relayState } : {}
74
+ }) : await saml.validateRedirectAsync(Object.fromEntries(url.searchParams), originalQuery);
75
+ profile = res.profile;
76
+ } catch (e) {
77
+ return {
78
+ kind: "denied",
79
+ reason: `logout request rejected: ${e instanceof Error ? e.message : String(e)}`
80
+ };
81
+ }
82
+ if (!profile) {
83
+ return { kind: "denied", reason: "logout request produced no profile" };
84
+ }
85
+ if (!profile.ID) {
86
+ return {
87
+ kind: "error",
88
+ error: authError.internalError("saml-sp: LogoutRequest carries no @ID — front-channel replay " + "protection cannot be established. Refusing to process.")
89
+ };
90
+ }
91
+ {
92
+ const key = `slo-replay:${profile.ID}`;
93
+ const seen = await ctx.methodScratch.get(key);
94
+ if (seen.ok) {
95
+ return {
96
+ kind: "denied",
97
+ reason: "logout request replay detected (request ID already seen)"
98
+ };
99
+ }
100
+ const recorded = await ctx.methodScratch.put(key, "1", SLO_REPLAY_HORIZON_MS);
101
+ if (isErr(recorded)) {
102
+ return {
103
+ kind: "error",
104
+ error: authError.internalError("saml-sp: could not record LogoutRequest ID for replay protection")
105
+ };
106
+ }
107
+ }
108
+ let logoutResponseUrl;
109
+ try {
110
+ logoutResponseUrl = await saml.getLogoutResponseUrlAsync(profile, relayState ?? "", {}, true);
111
+ } catch (e) {
112
+ return {
113
+ kind: "error",
114
+ error: authError.internalError(`saml-sp: failed to build LogoutResponse: ${e instanceof Error ? e.message : String(e)}`, e)
115
+ };
116
+ }
117
+ return {
118
+ kind: "challenge",
119
+ response: new Response(null, {
120
+ status: 302,
121
+ headers: { location: logoutResponseUrl }
122
+ }),
123
+ logout: {
124
+ ...profile.nameID !== undefined ? { nameId: profile.nameID } : {},
125
+ ...profile.sessionIndex !== undefined ? { sessionIndex: profile.sessionIndex } : {}
126
+ }
127
+ };
128
+ }
129
+ try {
130
+ if (isPost) {
131
+ await saml.validatePostResponseAsync({
132
+ SAMLResponse: samlResponse,
133
+ ...relayState !== null ? { RelayState: relayState } : {}
134
+ });
135
+ } else {
136
+ await saml.validateRedirectAsync(Object.fromEntries(url.searchParams), originalQuery);
137
+ }
138
+ } catch (e) {
139
+ return {
140
+ kind: "denied",
141
+ reason: `logout response rejected: ${e instanceof Error ? e.message : String(e)}`
142
+ };
143
+ }
144
+ return {
145
+ kind: "challenge",
146
+ response: new Response("Logged out.", {
147
+ status: 200,
148
+ headers: { "content-type": "text/plain" }
149
+ })
150
+ };
151
+ }
152
+ export {
153
+ consumeSls
154
+ };
File without changes
@@ -4,8 +4,8 @@ var err = (error) => ({ ok: false, error });
4
4
  var isOk = (r) => r.ok === true;
5
5
  var isErr = (r) => r.ok === false;
6
6
  export {
7
- ok,
8
- isOk,
7
+ err,
9
8
  isErr,
10
- err
9
+ isOk,
10
+ ok
11
11
  };
@@ -246,8 +246,8 @@ function renderForm(opts) {
246
246
  });
247
247
  }
248
248
  export {
249
- renderForm,
250
- htmlPage,
249
+ BASE_STYLES,
251
250
  escapeHtml,
252
- BASE_STYLES
251
+ htmlPage,
252
+ renderForm
253
253
  };
@@ -1,8 +1,11 @@
1
+ // src/ui/index.ts
2
+ import { renderForm, escapeHtml, htmlPage, BASE_STYLES } from "./forms";
3
+ import { renderPicker, buildMethodLink } from "./picker";
1
4
  export {
2
- renderPicker,
3
- renderForm,
4
- htmlPage,
5
- escapeHtml,
5
+ BASE_STYLES,
6
6
  buildMethodLink,
7
- BASE_STYLES
7
+ escapeHtml,
8
+ htmlPage,
9
+ renderForm,
10
+ renderPicker
8
11
  };
@@ -44,6 +44,6 @@ function prettyKind(kind) {
44
44
  return kind.split(/[-_]/).map((p) => p.length === 0 ? p : p[0].toUpperCase() + p.slice(1)).join(" ");
45
45
  }
46
46
  export {
47
- renderPicker,
48
- buildMethodLink
47
+ buildMethodLink,
48
+ renderPicker
49
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../../src/adapters/d1/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,eAAO,MAAM,kBAAkB,UAkE9B,CAAA;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../../src/adapters/d1/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,eAAO,MAAM,kBAAkB,UAuE9B,CAAA;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D"}
@@ -16,5 +16,8 @@ export declare class D1SessionStore implements SessionStore {
16
16
  createSession(sessionId: string, payload: SessionRecord, ttl: number): Promise<Result<void>>;
17
17
  readSession(sessionId: string): Promise<Result<SessionRecord>>;
18
18
  revokeSession(sessionId: string): Promise<Result<void>>;
19
+ saveScratch(key: string, value: string, ttlMs: number): Promise<Result<void>>;
20
+ readScratch(key: string): Promise<Result<string>>;
21
+ deleteScratch(key: string): Promise<Result<void>>;
19
22
  }
20
23
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/d1/session-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAIhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,aAAa,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,cAAe,YAAW,YAAY;;gBAIrC,IAAI,EAAE,qBAAqB;IAKjC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAwDlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IA2BrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAuBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAuBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IA2B9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAW9D"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/d1/session-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAIhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,aAAa,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,cAAe,YAAW,YAAY;;gBAIrC,IAAI,EAAE,qBAAqB;IAKjC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAwDlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IA2BrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAuBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAuBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IA2B9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAYvD,WAAW,CACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAyBlB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IA2BjD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAWxD"}
@@ -16,6 +16,9 @@ export declare class DurableObjectSessionStore implements SessionStore {
16
16
  createSession(sessionId: string, payload: SessionRecord, ttl: number): Promise<Result<void>>;
17
17
  readSession(sessionId: string): Promise<Result<SessionRecord>>;
18
18
  revokeSession(sessionId: string): Promise<Result<void>>;
19
+ saveScratch(key: string, value: string, ttlMs: number): Promise<Result<void>>;
20
+ readScratch(key: string): Promise<Result<string>>;
21
+ deleteScratch(key: string): Promise<Result<void>>;
19
22
  }
20
23
  export type { DurableObjectStorageLike, DurableObjectTransactionLike };
21
24
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/durable-object/session-store.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,KAAK,EACV,wBAAwB,EACxB,4BAA4B,EAC7B,MAAM,SAAS,CAAA;AAYhB,MAAM,MAAM,gCAAgC,GAAG;IAC7C,OAAO,EAAE,wBAAwB,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,yBAA0B,YAAW,YAAY;;gBAIhD,IAAI,EAAE,gCAAgC;IAK5C,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAkBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IA2BlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAmBrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IA+BxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAmB9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAQ9D;AAMD,YAAY,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,CAAA"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/durable-object/session-store.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,KAAK,EACV,wBAAwB,EACxB,4BAA4B,EAC7B,MAAM,SAAS,CAAA;AAiBhB,MAAM,MAAM,gCAAgC,GAAG;IAC7C,OAAO,EAAE,wBAAwB,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,yBAA0B,YAAW,YAAY;;gBAIhD,IAAI,EAAE,gCAAgC;IAK5C,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAkBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IA2BlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAmBrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IA+BxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAmB9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IASvD,WAAW,CACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAuBlB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAmBjD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAQxD;AAOD,YAAY,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,CAAA"}
@@ -16,5 +16,8 @@ export declare class DynamoSessionStore implements SessionStore {
16
16
  createSession(sessionId: string, payload: SessionRecord, ttl: number): Promise<Result<void>>;
17
17
  readSession(sessionId: string): Promise<Result<SessionRecord>>;
18
18
  revokeSession(sessionId: string): Promise<Result<void>>;
19
+ saveScratch(key: string, value: string, ttlMs: number): Promise<Result<void>>;
20
+ readScratch(key: string): Promise<Result<string>>;
21
+ deleteScratch(key: string): Promise<Result<void>>;
19
22
  }
20
23
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/dynamo/session-store.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,kBAAmB,YAAW,YAAY;;gBAIzC,IAAI,EAAE,yBAAyB;IAKrC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAuBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoDlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAsBrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAkBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAyBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAsB9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAQ9D"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/dynamo/session-store.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB,CAAA;AAED,qBAAa,kBAAmB,YAAW,YAAY;;gBAIzC,IAAI,EAAE,yBAAyB;IAKrC,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAuBlB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAoDlB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAsBrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAkBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAyBlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAsB9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IASvD,WAAW,CACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IA0BlB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAwBjD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAQxD"}
@@ -17,5 +17,8 @@ export declare class MemorySessionStore implements SessionStore {
17
17
  revokeSession(sessionId: string): Promise<Result<void>>;
18
18
  savePar(requestUri: string, payload: ParRecord, ttl: number): Promise<Result<void>>;
19
19
  consumePar(requestUri: string): Promise<Result<ParRecord>>;
20
+ saveScratch(key: string, value: string, ttlMs: number): Promise<Result<void>>;
21
+ readScratch(key: string): Promise<Result<string>>;
22
+ deleteScratch(key: string): Promise<Result<void>>;
20
23
  }
21
24
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/memory/session-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,YAAY,EACb,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAapC,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAED,qBAAa,kBAAmB,YAAW,YAAY;;gBAMzC,IAAI,GAAE,yBAA8B;IAI1C,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAYrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAgBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAYlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAY9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAKvD,OAAO,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;CAejE"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../../src/adapters/memory/session-store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,YAAY,EACb,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAkBpC,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAED,qBAAa,kBAAmB,YAAW,YAAY;;gBAOzC,IAAI,GAAE,yBAA8B;IAI1C,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAYrD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAgBxD,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAYlB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAY9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAKvD,OAAO,CACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAalB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAgB1D,WAAW,CACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAYlB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAYjD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAIxD"}
@@ -12,7 +12,7 @@
12
12
  * comparisons are simple integer math.
13
13
  */
14
14
  import type { PostgresExecutor } from "./executor";
15
- export declare const INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS openauth_tenants (\n id text PRIMARY KEY,\n config jsonb NOT NULL,\n updated_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_methods (\n tenant_id text NOT NULL,\n method_id text NOT NULL,\n config jsonb NOT NULL,\n updated_at bigint NOT NULL,\n PRIMARY KEY (tenant_id, method_id)\n);\n\nCREATE TABLE IF NOT EXISTS openauth_signing_keys (\n kid text PRIMARY KEY,\n alg text NOT NULL,\n crv text,\n public_jwk jsonb NOT NULL,\n private_jwk jsonb NOT NULL,\n private_jwk_wrapped boolean NOT NULL DEFAULT false,\n status text NOT NULL,\n created_at bigint NOT NULL,\n rotated_at bigint\n);\n\nALTER TABLE openauth_signing_keys\n ADD COLUMN IF NOT EXISTS private_jwk_wrapped boolean NOT NULL DEFAULT false;\n\nCREATE TABLE IF NOT EXISTS openauth_encryption_keys (\n kid text PRIMARY KEY,\n alg text NOT NULL,\n key_material bytea NOT NULL,\n key_material_wrapped boolean NOT NULL DEFAULT false,\n status text NOT NULL,\n created_at bigint NOT NULL,\n rotated_at bigint\n);\n\nALTER TABLE openauth_encryption_keys\n ADD COLUMN IF NOT EXISTS key_material_wrapped boolean NOT NULL DEFAULT false;\n\nCREATE TABLE IF NOT EXISTS openauth_codes (\n code text PRIMARY KEY,\n ciphertext text NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_refresh_tokens (\n token text PRIMARY KEY,\n tenant_id text NOT NULL,\n client_id text NOT NULL,\n subject_id text NOT NULL,\n family text NOT NULL,\n expires_at bigint NOT NULL,\n consumed_at bigint,\n payload jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS openauth_refresh_family_idx\n ON openauth_refresh_tokens (family);\nCREATE INDEX IF NOT EXISTS openauth_refresh_subject_idx\n ON openauth_refresh_tokens (tenant_id, subject_id);\n\nCREATE TABLE IF NOT EXISTS openauth_flows (\n flow_id text PRIMARY KEY,\n payload jsonb NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_sessions (\n session_id text PRIMARY KEY,\n payload jsonb NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_audit_events (\n id bigserial PRIMARY KEY,\n ts bigint NOT NULL,\n kind text NOT NULL,\n payload jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS openauth_audit_kind_idx\n ON openauth_audit_events (kind);\nCREATE INDEX IF NOT EXISTS openauth_audit_ts_idx\n ON openauth_audit_events (ts);\n\nCREATE TABLE IF NOT EXISTS openauth_passkey_credentials (\n tenant_id text NOT NULL,\n credential_id text NOT NULL,\n user_id text NOT NULL,\n public_key text NOT NULL,\n counter bigint NOT NULL DEFAULT 0,\n transports jsonb,\n created_at bigint NOT NULL,\n PRIMARY KEY (tenant_id, credential_id)\n);\nCREATE INDEX IF NOT EXISTS openauth_passkey_user_idx\n ON openauth_passkey_credentials (tenant_id, user_id);\n";
15
+ export declare const INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS openauth_tenants (\n id text PRIMARY KEY,\n config jsonb NOT NULL,\n updated_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_methods (\n tenant_id text NOT NULL,\n method_id text NOT NULL,\n config jsonb NOT NULL,\n updated_at bigint NOT NULL,\n PRIMARY KEY (tenant_id, method_id)\n);\n\nCREATE TABLE IF NOT EXISTS openauth_signing_keys (\n kid text PRIMARY KEY,\n alg text NOT NULL,\n crv text,\n public_jwk jsonb NOT NULL,\n private_jwk jsonb NOT NULL,\n private_jwk_wrapped boolean NOT NULL DEFAULT false,\n status text NOT NULL,\n created_at bigint NOT NULL,\n rotated_at bigint\n);\n\nALTER TABLE openauth_signing_keys\n ADD COLUMN IF NOT EXISTS private_jwk_wrapped boolean NOT NULL DEFAULT false;\n\nCREATE TABLE IF NOT EXISTS openauth_encryption_keys (\n kid text PRIMARY KEY,\n alg text NOT NULL,\n key_material bytea NOT NULL,\n key_material_wrapped boolean NOT NULL DEFAULT false,\n status text NOT NULL,\n created_at bigint NOT NULL,\n rotated_at bigint\n);\n\nALTER TABLE openauth_encryption_keys\n ADD COLUMN IF NOT EXISTS key_material_wrapped boolean NOT NULL DEFAULT false;\n\nCREATE TABLE IF NOT EXISTS openauth_codes (\n code text PRIMARY KEY,\n ciphertext text NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_refresh_tokens (\n token text PRIMARY KEY,\n tenant_id text NOT NULL,\n client_id text NOT NULL,\n subject_id text NOT NULL,\n family text NOT NULL,\n expires_at bigint NOT NULL,\n consumed_at bigint,\n payload jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS openauth_refresh_family_idx\n ON openauth_refresh_tokens (family);\nCREATE INDEX IF NOT EXISTS openauth_refresh_subject_idx\n ON openauth_refresh_tokens (tenant_id, subject_id);\n\nCREATE TABLE IF NOT EXISTS openauth_flows (\n flow_id text PRIMARY KEY,\n payload jsonb NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_sessions (\n session_id text PRIMARY KEY,\n payload jsonb NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_scratch (\n scratch_key text PRIMARY KEY,\n value text NOT NULL,\n expires_at bigint NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS openauth_audit_events (\n id bigserial PRIMARY KEY,\n ts bigint NOT NULL,\n kind text NOT NULL,\n payload jsonb NOT NULL\n);\nCREATE INDEX IF NOT EXISTS openauth_audit_kind_idx\n ON openauth_audit_events (kind);\nCREATE INDEX IF NOT EXISTS openauth_audit_ts_idx\n ON openauth_audit_events (ts);\n\nCREATE TABLE IF NOT EXISTS openauth_passkey_credentials (\n tenant_id text NOT NULL,\n credential_id text NOT NULL,\n user_id text NOT NULL,\n public_key text NOT NULL,\n counter bigint NOT NULL DEFAULT 0,\n transports jsonb,\n created_at bigint NOT NULL,\n PRIMARY KEY (tenant_id, credential_id)\n);\nCREATE INDEX IF NOT EXISTS openauth_passkey_user_idx\n ON openauth_passkey_credentials (tenant_id, user_id);\n";
16
16
  /**
17
17
  * Apply the initial schema. Idempotent — uses `CREATE TABLE IF NOT EXISTS`.
18
18
  * Deployments using a real migration tool can apply the SQL above directly
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../../src/adapters/postgres/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,eAAO,MAAM,kBAAkB,mwFAmG9B,CAAA;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASnE"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../../src/adapters/postgres/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,eAAO,MAAM,kBAAkB,+4FAyG9B,CAAA;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CASnE"}