okengine 0.17.2 → 0.18.4

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 (209) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +14 -10
  3. package/manifest.v1.schema.json +61 -2
  4. package/package.json +19 -5
  5. package/site/content/docs/ai/index.mdx +1 -1
  6. package/site/content/docs/ai/mcp.mdx +10 -1
  7. package/site/content/docs/ai/skills.mdx +9 -9
  8. package/site/content/docs/elements/ai.mdx +1 -1
  9. package/site/content/docs/elements/clock.mdx +1 -1
  10. package/site/content/docs/elements/flow.mdx +25 -1
  11. package/site/content/docs/elements/gate.mdx +3 -2
  12. package/site/content/docs/elements/store.mdx +289 -341
  13. package/site/content/docs/elements/vault.mdx +5 -5
  14. package/site/content/docs/get-started/basic-usage.mdx +3 -10
  15. package/site/content/docs/get-started/index.mdx +1 -1
  16. package/site/content/docs/get-started/installation.mdx +2 -3
  17. package/site/content/docs/get-started/introduction.mdx +58 -121
  18. package/site/content/docs/get-started/meta.json +9 -1
  19. package/site/content/docs/get-started/project-structure.mdx +4 -11
  20. package/site/content/docs/get-started/testing.mdx +328 -0
  21. package/site/content/docs/get-started/why.mdx +93 -71
  22. package/site/content/docs/index.mdx +44 -11
  23. package/site/content/docs/meta.json +8 -5
  24. package/site/content/docs/plugins/apple.mdx +151 -0
  25. package/site/content/docs/plugins/discord.mdx +139 -0
  26. package/site/content/docs/plugins/facebook.mdx +134 -0
  27. package/site/content/docs/plugins/figma.mdx +138 -0
  28. package/site/content/docs/plugins/github.mdx +138 -0
  29. package/site/content/docs/plugins/google.mdx +153 -0
  30. package/site/content/docs/plugins/index.mdx +47 -1
  31. package/site/content/docs/plugins/meta.json +10 -0
  32. package/site/content/docs/plugins/microsoft.mdx +151 -0
  33. package/site/content/docs/plugins/oauth.mdx +188 -0
  34. package/site/content/docs/plugins/x.mdx +125 -0
  35. package/site/content/docs/providers/index.mdx +2 -0
  36. package/site/content/docs/recipes/index.mdx +2 -0
  37. package/site/content/docs/reference/cli.md +3 -2
  38. package/site/content/docs/reference/client.mdx +58 -1
  39. package/site/content/docs/reference/configuration.mdx +2 -4
  40. package/site/content/docs/reference/fx.mdx +3 -1
  41. package/site/content/docs/reference/index.mdx +0 -5
  42. package/site/content/docs/reference/meta.json +2 -2
  43. package/site/content/docs/reference/okid.mdx +137 -0
  44. package/src/auth/bindings.ts +1 -1
  45. package/src/auth/config.ts +9 -0
  46. package/src/auth/identity-sql.ts +314 -0
  47. package/src/auth/identity.ts +140 -2
  48. package/src/auth/index.ts +17 -1
  49. package/src/auth/method-context.ts +3 -0
  50. package/src/auth/oauth-as/cimd.ts +132 -0
  51. package/src/auth/oauth-as/crypto.test.ts +101 -0
  52. package/src/auth/oauth-as/crypto.ts +393 -0
  53. package/src/auth/oauth-as/errors.ts +68 -0
  54. package/src/auth/oauth-as/http.test.ts +419 -0
  55. package/src/auth/oauth-as/http.ts +842 -0
  56. package/src/auth/oauth-as/stores.ts +61 -0
  57. package/src/auth/oauth-as/tables.ts +142 -0
  58. package/src/auth/tables.ts +0 -11
  59. package/src/bench/README.md +83 -0
  60. package/src/bench/REPORT.md +176 -0
  61. package/src/bench/g01-rls-stamp.bench.ts +194 -0
  62. package/src/bench/g02-clock-per-tenant.bench.ts +158 -0
  63. package/src/bench/g03-signal-once.bench.ts +157 -0
  64. package/src/bench/g03-signal-reconnect.bench.ts +254 -0
  65. package/src/bench/g03-signal-sse-memory.bench.ts +191 -0
  66. package/src/bench/g04-auth-vault-hotpath.bench.ts +170 -0
  67. package/src/bench/g05-sustained-full.bench.ts +265 -0
  68. package/src/bench/g06-mixed-load.bench.ts +260 -0
  69. package/src/bench/g07-vault-crypto.bench.ts +100 -0
  70. package/src/bench/g07-vault-rotate-under-read.bench.ts +285 -0
  71. package/src/bench/g08-conn-oversubscribe.bench.ts +194 -0
  72. package/src/bench/g08-store-kv-durable.bench.ts +133 -0
  73. package/src/bench/g08-store-sql.bench.ts +178 -0
  74. package/src/bench/g09-journal-sustained.bench.ts +203 -0
  75. package/src/bench/g10-observability-contention.bench.ts +246 -0
  76. package/src/bench/g11-cold-start-cycle.bench.ts +164 -0
  77. package/src/bench/g13-elements.bench.ts +427 -0
  78. package/src/bench/g14-graceful-shutdown.bench.ts +244 -0
  79. package/src/bench/g15-postgres-degradation.bench.ts +264 -0
  80. package/src/bench/g16-live-query-fanout.bench.ts +206 -0
  81. package/src/bench/lib/event-loop-lag.ts +26 -0
  82. package/src/bench/lib/infra.ts +60 -0
  83. package/src/bench/lib/report.ts +52 -0
  84. package/src/bench/lib/rss-sampler.ts +61 -0
  85. package/src/bench/lib/signal-pg.ts +88 -0
  86. package/src/bench/load-app.ts +337 -0
  87. package/src/bench/load-child.ts +108 -0
  88. package/src/bench/smoke.bench.ts +43 -0
  89. package/src/cli/competitor-mention-removal.test.ts +28 -0
  90. package/src/cli/doctor-fd.ts +117 -0
  91. package/src/cli/doctor.test.ts +192 -0
  92. package/src/cli/doctor.ts +129 -1
  93. package/src/client/create.ts +95 -1
  94. package/src/client/index.ts +9 -2
  95. package/src/client/transport.ts +11 -4
  96. package/src/client/use-live-query.ts +154 -0
  97. package/src/client-react/index.ts +15 -1
  98. package/src/client-react/live-resource.ts +246 -0
  99. package/src/client-react/use-live-query.test.ts +475 -0
  100. package/src/client-react/use-live-query.ts +530 -0
  101. package/src/compiler/extract.test.ts +518 -0
  102. package/src/compiler/extract.ts +386 -19
  103. package/src/console/server/invoke-user-flow.ts +2 -1
  104. package/src/console/ui-next/dist/assets/{access-page-DnWbnGzq.js → access-page-De7Lc2JC.js} +1 -1
  105. package/src/console/ui-next/dist/assets/{flows-page-BiZ4-6yQ.js → flows-page-RGy7VEA_.js} +1 -1
  106. package/src/console/ui-next/dist/assets/{index-C8NRK2R-.js → index-_rgpdVzo.js} +3 -3
  107. package/src/console/ui-next/dist/assets/{observability-page-CrB6vd1T.js → observability-page-Ds6pcnh-.js} +1 -1
  108. package/src/console/ui-next/dist/assets/{store-page-CS5-aETQ.js → store-page-02xOiqIK.js} +3 -3
  109. package/src/console/ui-next/dist/assets/{units-page-CjtdlW8l.js → units-page-4rHOePuE.js} +1 -1
  110. package/src/console/ui-next/dist/assets/{vault-page-C6Xxm9SA.js → vault-page-DISPgxLM.js} +1 -1
  111. package/src/console/ui-next/dist/index.html +1 -1
  112. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +36 -2
  113. package/src/drivers/cdc-outbox.ts +389 -0
  114. package/src/drivers/memory.ts +20 -0
  115. package/src/drivers/oauth-apple.ts +156 -0
  116. package/src/drivers/oauth-discord.ts +79 -0
  117. package/src/drivers/oauth-facebook.ts +80 -0
  118. package/src/drivers/oauth-figma.ts +116 -0
  119. package/src/drivers/oauth-github.ts +92 -0
  120. package/src/drivers/oauth-google.ts +142 -0
  121. package/src/drivers/oauth-microsoft.ts +174 -0
  122. package/src/drivers/oauth-oidc.ts +293 -0
  123. package/src/drivers/oauth-shared.ts +326 -0
  124. package/src/drivers/oauth-types.ts +159 -0
  125. package/src/drivers/oauth-x.ts +77 -0
  126. package/src/drivers/oauth2-common.ts +95 -0
  127. package/src/drivers/oauth2-token.ts +61 -0
  128. package/src/drivers/pg-rls-row-passes.ts +251 -0
  129. package/src/drivers/pg-rls.ts +2 -0
  130. package/src/drivers/postgres.ts +45 -2
  131. package/src/drivers/signal-postgres.ts +2 -1
  132. package/src/elements/channel/runtime.ts +29 -2
  133. package/src/elements/channel.test.ts +52 -0
  134. package/src/elements/gate/boot.ts +29 -2
  135. package/src/elements/store/emit-drizzle.ts +147 -14
  136. package/src/elements/store/field-ddl.test.ts +118 -0
  137. package/src/elements/store/field-types.test.ts +455 -0
  138. package/src/elements/store/list-query.golden.json +777 -0
  139. package/src/elements/store/list-query.parity.test.ts +396 -0
  140. package/src/elements/store/list-query.ts +792 -0
  141. package/src/elements/store/live-default.test.ts +136 -0
  142. package/src/elements/store/live-http.test.ts +160 -0
  143. package/src/elements/store/live-isolation.test.ts +291 -0
  144. package/src/elements/store/live-query-runtime.test.ts +323 -0
  145. package/src/elements/store/live-query-runtime.ts +403 -0
  146. package/src/elements/store/live-query-server.test.ts +377 -0
  147. package/src/elements/store/live-query-server.ts +102 -0
  148. package/src/elements/store/live-query.ts +97 -0
  149. package/src/elements/store/resource.ts +189 -680
  150. package/src/elements/store/rls-row-passes-policies.parity.test.ts +665 -0
  151. package/src/elements/store/schema-decl.ts +539 -41
  152. package/src/elements/store/sql-rls-stamp.test.ts +27 -0
  153. package/src/elements/store/sql-session.ts +297 -35
  154. package/src/elements/store/table.ts +102 -21
  155. package/src/elements/store.test.ts +3 -1
  156. package/src/elements/store.ts +12 -1
  157. package/src/elements/vault/chaos-child.ts +74 -1
  158. package/src/elements/vault/chaos.test.ts +4 -2
  159. package/src/elements/vault/storage.ts +4 -2
  160. package/src/index.ts +5 -2
  161. package/src/kernel/app-auth.ts +1 -0
  162. package/src/kernel/app.ts +116 -2
  163. package/src/kernel/auth-sharing.test.ts +196 -0
  164. package/src/kernel/boot.test.ts +3 -3
  165. package/src/kernel/errors.ts +8 -0
  166. package/src/kernel/fx.test.ts +1 -0
  167. package/src/kernel/fx.ts +14 -2
  168. package/src/kernel/horizontal-child.ts +2 -1
  169. package/src/kernel/http-resource.ts +33 -7
  170. package/src/kernel/identity-host-persist.test.ts +119 -0
  171. package/src/kernel/instance-id.ts +4 -2
  172. package/src/kernel/journal.ts +2 -1
  173. package/src/kernel/mcp-tool.test.ts +95 -0
  174. package/src/kernel/on.ts +9 -0
  175. package/src/kernel/realtime-bind.ts +326 -0
  176. package/src/kernel/resource-live.ts +117 -0
  177. package/src/kernel/triggers.ts +86 -4
  178. package/src/manifest/diff.ts +37 -0
  179. package/src/manifest/types.ts +64 -2
  180. package/src/okid.bench.test.ts +64 -0
  181. package/src/okid.test.ts +338 -0
  182. package/src/okid.ts +245 -0
  183. package/src/plugins/anonymous.ts +19 -1
  184. package/src/plugins/auth/shared.ts +15 -0
  185. package/src/plugins/index.ts +2 -0
  186. package/src/plugins/magic-link.ts +10 -8
  187. package/src/plugins/mcp-oauth.ts +208 -0
  188. package/src/plugins/oauth/flow-store.ts +117 -0
  189. package/src/plugins/oauth/link.ts +69 -0
  190. package/src/plugins/oauth/shared.ts +108 -0
  191. package/src/plugins/oauth/token-vault.ts +100 -0
  192. package/src/plugins/oauth.security.test.ts +535 -0
  193. package/src/plugins/oauth.ts +532 -0
  194. package/src/plugins/otp.ts +48 -6
  195. package/src/plugins/passkey.ts +20 -1
  196. package/src/plugins/two-factor.ts +11 -0
  197. package/src/plugins/username.ts +40 -7
  198. package/src/release/build-lib.ts +7 -1
  199. package/src/release/measure.ts +1 -0
  200. package/src/release/official-plugins.ts +4 -1
  201. package/src/runs/collect.ts +2 -1
  202. package/src/runs/drivers/files.ts +2 -1
  203. package/src/test/create-test-app.ts +114 -5
  204. package/src/test/export-bundle.test.ts +33 -0
  205. package/src/test/live-signals.test.ts +83 -0
  206. package/src/test/tenant-isolation.test.ts +175 -0
  207. package/src/testing.ts +26 -0
  208. package/src/upgrade/codemods.ts +1 -1
  209. package/site/content/docs/reference/migrating-environments.mdx +0 -158
@@ -0,0 +1,117 @@
1
+ /**
2
+ * OAuth flow records on the shared verification store — PKCE verifier,
3
+ * state, nonce, expected issuer, exact redirect URI and optional linking
4
+ * subject, keyed `oauth:{provider}:{stateHash}` with SHA-256-hashed state at
5
+ * rest (same hygiene as magic-link/OTP rows). Single-use via consume.
6
+ */
7
+
8
+ import {
9
+ createVerificationStore,
10
+ findActiveVerification,
11
+ consumeVerification,
12
+ hashChallenge,
13
+ putVerification,
14
+ type VerificationStore,
15
+ } from "../../auth/verification.ts";
16
+ import type { OAuthProviderId } from "./shared.ts";
17
+
18
+ /** TTL for a pending OAuth authorization flow. */
19
+ export const OAUTH_FLOW_TTL_MS = 10 * 60_000;
20
+
21
+ /** One pending OAuth authorization-code flow. */
22
+ export interface OAuthFlowRecord {
23
+ readonly provider: OAuthProviderId;
24
+ /** SHA-256 hex of the raw state — the row's secret value. */
25
+ readonly stateHash: string;
26
+ readonly codeVerifier: string;
27
+ /** EXACT redirect URI string registered at the provider. */
28
+ readonly redirectUri: string;
29
+ readonly nonce?: string;
30
+ /** Issuer the flow was initiated against (mix-up anchor). */
31
+ readonly expectedIssuer: string;
32
+ /** Client id used to start the flow (aud check). */
33
+ readonly clientId: string;
34
+ /** Authenticated user linking this login to an existing account. */
35
+ readonly currentUserId?: string;
36
+ }
37
+
38
+ /**
39
+ * Create (or reuse) a verification store backing OAuth flows.
40
+ *
41
+ * @param existing - Shared store when the host app provides one
42
+ */
43
+ export function createOAuthFlowStore(existing?: VerificationStore): VerificationStore {
44
+ return existing ?? createVerificationStore();
45
+ }
46
+
47
+ /**
48
+ * Persist a new flow record.
49
+ *
50
+ * @param store - Verification store
51
+ * @param record - Flow fields
52
+ * @param now - Epoch-ms
53
+ */
54
+ export async function putOauthFlow(
55
+ store: VerificationStore,
56
+ record: OAuthFlowRecord,
57
+ now: number,
58
+ ): Promise<void> {
59
+ putVerification(store, {
60
+ id: `oauth:${record.provider}:${record.stateHash}`,
61
+ identifier: `oauth:${record.provider}`,
62
+ value: record.stateHash,
63
+ expiresAt: now + OAUTH_FLOW_TTL_MS,
64
+ createdAt: now,
65
+ consumedAt: null,
66
+ attempts: 0,
67
+ // Flow fields ride the row's JSON slot (same pattern as channel-neutral OTP).
68
+ email: JSON.stringify(record),
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Find the active (non-consumed, non-expired) flow row for `(provider, rawState)`.
74
+ *
75
+ * @param store - Verification store
76
+ * @param provider - Route provider id
77
+ * @param rawState - Raw `state` query/form value
78
+ * @param now - Epoch-ms
79
+ */
80
+ export async function findOauthFlow(
81
+ store: VerificationStore,
82
+ provider: OAuthProviderId,
83
+ rawState: string,
84
+ now: number,
85
+ ): Promise<OAuthFlowRecord | undefined> {
86
+ const stateHash = await hashChallenge(rawState);
87
+ for (const row of store.rows.values()) {
88
+ if (row.identifier !== `oauth:${provider}`) continue;
89
+ if (row.consumedAt !== null || row.expiresAt <= now) continue;
90
+ if (row.value !== stateHash || row.email === undefined || row.email === null) continue;
91
+ try {
92
+ return JSON.parse(row.email) as OAuthFlowRecord;
93
+ } catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ return undefined;
98
+ }
99
+
100
+ /**
101
+ * Mark a flow row consumed (single-use — replay returns `invalid_state`).
102
+ *
103
+ * @param store - Verification store
104
+ * @param provider - Route provider id
105
+ * @param rawState - Raw state
106
+ * @param now - Epoch-ms
107
+ */
108
+ export async function consumeOauthFlow(
109
+ store: VerificationStore,
110
+ provider: OAuthProviderId,
111
+ rawState: string,
112
+ now: number,
113
+ ): Promise<void> {
114
+ const stateHash = await hashChallenge(rawState);
115
+ const row = findActiveVerification(store, `oauth:${provider}`, now);
116
+ if (row && row.value === stateHash) consumeVerification(row, now);
117
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Identity bridge — every successful OAuth callback routes through
3
+ * `linkOrProvision`; driver assertions never write identities directly.
4
+ * `IdentityError` codes map onto the typed `AuthFailed` error surface.
5
+ */
6
+
7
+ import { IdentityError, linkOrProvision, type IdentityStore } from "../../auth/identity.ts";
8
+ import type { OAuthAssertion } from "../../drivers/oauth-types.ts";
9
+ import type { OAuthProviderId } from "./shared.ts";
10
+
11
+ /**
12
+ * Credential provider string namespacing OAuth accounts
13
+ * (`oke_credentials.provider = "oauth:google"` etc.).
14
+ *
15
+ * @param provider - Plugin provider id
16
+ */
17
+ export function oauthCredentialProvider(provider: OAuthProviderId): string {
18
+ return `oauth:${provider}`;
19
+ }
20
+
21
+ /** Outcome of the identity bridge. */
22
+ export interface OauthLinkResult {
23
+ readonly userId: string;
24
+ }
25
+
26
+ /**
27
+ * Route a verified assertion through `linkOrProvision`.
28
+ *
29
+ * GHSA-6g38-8j4p-j3pr defense: `emailVerified` comes verbatim from the
30
+ * driver trust matrix (default false), and `linkOrProvision` refuses to take
31
+ * over an existing email (`email_in_use`) unless `currentUserId` proves the
32
+ * caller already owns the account.
33
+ *
34
+ * @param identities - Shared identity store
35
+ * @param provider - Provider id
36
+ * @param assertion - Verified driver assertion
37
+ * @param currentUserId - Authenticated subject when linking from a session
38
+ */
39
+ export async function oauthLink(
40
+ identities: IdentityStore,
41
+ provider: OAuthProviderId,
42
+ assertion: OAuthAssertion,
43
+ currentUserId?: string,
44
+ ): Promise<OauthLinkResult> {
45
+ const { user } = await linkOrProvision(identities, {
46
+ provider: oauthCredentialProvider(provider),
47
+ providerAccountId: assertion.subject,
48
+ ...(assertion.email !== undefined ? { email: assertion.email } : {}),
49
+ emailVerified: assertion.emailVerified,
50
+ ...(assertion.name !== undefined ? { name: assertion.name } : {}),
51
+ ...(currentUserId !== undefined ? { currentUserId } : {}),
52
+ });
53
+ return { userId: user.id };
54
+ }
55
+
56
+ /**
57
+ * Map an {@link IdentityError} code onto a client-safe failure reason.
58
+ *
59
+ * @param err - Identity error
60
+ */
61
+ export function identityFailureReason(err: unknown): string | undefined {
62
+ if (err instanceof IdentityError) {
63
+ // Enumeration hygiene: never disclose which emails exist.
64
+ if (err.code === "email_in_use" || err.code === "email_conflict") return "email_in_use";
65
+ if (err.code === "identity_not_found") return "invalid_credentials";
66
+ return err.code;
67
+ }
68
+ return undefined;
69
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * OAuth plugin shared surface: options, provider ids, PKCE minting.
3
+ * Backend-only — never imported by Console UI.
4
+ */
5
+
6
+ import type { VerificationStore } from "../../auth/verification.ts";
7
+ import type { SessionStore } from "../../auth/sessions.ts";
8
+ import { generateCodeVerifier, codeChallengeS256 } from "../../drivers/oauth-shared.ts";
9
+ import type { OAuthDriverId } from "../../drivers/oauth-types.ts";
10
+ import type { AnyFlowDef } from "../../kernel/flow.ts";
11
+ import { bindAuthHttp } from "../../auth/bindings.ts";
12
+ import { http } from "../auth/shared.ts";
13
+ import type { Binding } from "../../kernel/on.ts";
14
+
15
+ /** Every supported social provider. */
16
+ export const OAUTH_PROVIDER_IDS = [
17
+ "google",
18
+ "apple",
19
+ "microsoft",
20
+ "github",
21
+ "discord",
22
+ "x",
23
+ "facebook",
24
+ "figma",
25
+ ] as const;
26
+
27
+ /** Supported social providers (same set as {@link OAuthDriverId}). */
28
+ export type OAuthProviderId = OAuthDriverId;
29
+
30
+ /** Per-provider plugin configuration. */
31
+ export interface OAuthProviderConfig {
32
+ /** Enable this provider (default `false`). */
33
+ readonly enabled?: boolean;
34
+ /**
35
+ * Confidential client id. Public information — inline config is fine;
36
+ * `OAUTH_<PROVIDER>_CLIENT_ID` via Vault/env also resolves at boot.
37
+ */
38
+ readonly clientId?: string;
39
+ /**
40
+ * EXACT redirect URI string registered at the provider console. Compared
41
+ * byte-for-byte — never derived from the request. Defaults to
42
+ * `${baseUrl}/auth/oauth/callback/{provider}`.
43
+ */
44
+ readonly redirectUri?: string;
45
+ /** Scopes requested at the provider (driver defaults apply when omitted). */
46
+ readonly scopes?: readonly string[];
47
+ /** Opt-in storage of provider tokens in Vault (encrypted), default off. */
48
+ readonly storeProviderTokens?: boolean;
49
+ /** Microsoft only — tenant selector or GUID (default `common`). */
50
+ readonly tenant?: "common" | "organizations" | "consumers" | string;
51
+ /** Apple only — Developer team id (client-secret JWT `iss`). */
52
+ readonly teamId?: string;
53
+ /** Apple only — Sign in with Apple private-key id (`kid`). */
54
+ readonly keyId?: string;
55
+ }
56
+
57
+ /** Options for the {@link oauth} plugin factory. */
58
+ export interface OAuthOptions {
59
+ /** Per-provider configuration; providers default to disabled. */
60
+ readonly providers?: Readonly<Partial<Record<OAuthProviderId, OAuthProviderConfig>>>;
61
+ /**
62
+ * Default app origin used to synthesize per-provider callback redirect
63
+ * URIs (`{baseUrl}/auth/oauth/callback/{provider}`).
64
+ */
65
+ readonly baseUrl?: string;
66
+ /** HMAC secret; falls back to the active `gate.auth` secret like other methods. */
67
+ readonly secret?: string;
68
+ /** Shared session store (prefer the same store as `gate.auth`). */
69
+ readonly sessions?: SessionStore;
70
+ /** Shared identity/credential store (defaults to `gate.auth.identities`). */
71
+ readonly identities?: import("../../auth/identity.ts").IdentityStore;
72
+ /** Shared verification store backing OAuth flow rows (tests / advanced). */
73
+ readonly verifications?: VerificationStore;
74
+ /** Injectable clock. */
75
+ readonly now?: () => number;
76
+ /** Injectable fetch for every driver (tests — mock IdP). */
77
+ readonly fetch?: typeof globalThis.fetch;
78
+ }
79
+
80
+ /**
81
+ * Fresh PKCE pair for a new authorization-code flow (RFC 7636 S256).
82
+ */
83
+ export async function mintPkcePair(): Promise<{ verifier: string; challenge: string }> {
84
+ const verifier = generateCodeVerifier();
85
+ return { verifier, challenge: await codeChallengeS256(verifier) };
86
+ }
87
+
88
+ /**
89
+ * Bind an auth Flow to a public **GET** trigger — provider redirects carry
90
+ * `?code=&state=`. The POST twin (Apple form_post) is bound separately.
91
+ *
92
+ * @param path - Absolute route path
93
+ * @param flowDef - Flow to bind
94
+ * @param gates - Optional gate decls (rate limiting)
95
+ */
96
+ export function bindPublicAuthGet(
97
+ path: string,
98
+ flowDef: AnyFlowDef,
99
+ gates: readonly Parameters<ReturnType<typeof http.get>["gate"]>[0][] = [],
100
+ ): Binding {
101
+ return bindAuthHttp(
102
+ http
103
+ .get(path)
104
+ .public()
105
+ .gate(...gates),
106
+ flowDef,
107
+ );
108
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Envelope sealing for opt-in provider-token storage.
3
+ *
4
+ * Vault paths hold one sealed blob per provider; inside, an AES-GCM-sealed
5
+ * map keyed by user id. The seal key is HKDF-derived from the auth secret
6
+ * with a domain-separated info string (never the session HMAC material) —
7
+ * same pattern as `otp-seal`.
8
+ */
9
+
10
+ /** HKDF `info` — versioned; bump when the envelope format changes. */
11
+ export const OAUTH_TOKEN_SEAL_HKDF_INFO = "oke-oauth-token-seal-v1";
12
+
13
+ const INFO_BYTES = new TextEncoder().encode(OAUTH_TOKEN_SEAL_HKDF_INFO);
14
+ const EMPTY_SALT = new Uint8Array(0);
15
+
16
+ async function deriveSealKey(secret: string): Promise<CryptoKey> {
17
+ const baseKey = await crypto.subtle.importKey(
18
+ "raw",
19
+ new TextEncoder().encode(secret),
20
+ "HKDF",
21
+ false,
22
+ ["deriveKey"],
23
+ );
24
+ return crypto.subtle.deriveKey(
25
+ { name: "HKDF", hash: "SHA-256", salt: EMPTY_SALT, info: INFO_BYTES },
26
+ baseKey,
27
+ { name: "AES-GCM", length: 256 },
28
+ false,
29
+ ["encrypt", "decrypt"],
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Seal a cleartext payload to `IV || ciphertext+tag`, base64-encoded.
35
+ *
36
+ * @param secret - Root auth secret
37
+ * @param plaintext - Cleartext bytes/string
38
+ */
39
+ export async function sealTokenBlob(secret: string, plaintext: string): Promise<string> {
40
+ const key = await deriveSealKey(secret);
41
+ const iv = crypto.getRandomValues(new Uint8Array(12));
42
+ const cipher = await crypto.subtle.encrypt(
43
+ { name: "AES-GCM", iv },
44
+ key,
45
+ new TextEncoder().encode(plaintext),
46
+ );
47
+ const packed = new Uint8Array(iv.length + cipher.byteLength);
48
+ packed.set(iv, 0);
49
+ packed.set(new Uint8Array(cipher), iv.length);
50
+ return Buffer.from(packed).toString("base64");
51
+ }
52
+
53
+ /**
54
+ * Open a blob produced by {@link sealTokenBlob}.
55
+ *
56
+ * @param secret - Root auth secret
57
+ * @param blob - Base64 sealed blob
58
+ */
59
+ export async function openTokenBlob(secret: string, blob: string): Promise<string> {
60
+ const key = await deriveSealKey(secret);
61
+ const packed = Buffer.from(blob, "base64");
62
+ if (packed.byteLength < 13) throw new Error("oauth-token-seal: malformed blob");
63
+ const iv = new Uint8Array(packed.subarray(0, 12));
64
+ const data = packed.subarray(12);
65
+ const plain = await crypto.subtle.decrypt(
66
+ { name: "AES-GCM", iv: toArrayBuffer(iv) },
67
+ key,
68
+ toArrayBuffer(data),
69
+ );
70
+ return new TextDecoder().decode(plain);
71
+ }
72
+
73
+ function toArrayBuffer(view: Uint8Array): ArrayBuffer {
74
+ const copy = new Uint8Array(view.byteLength);
75
+ copy.set(view);
76
+ return copy.buffer;
77
+ }
78
+
79
+ /**
80
+ * Seal a full per-user token map (the vault path value).
81
+ *
82
+ * @param secret - Root auth secret
83
+ * @param envelope - userId → sealed per-user blob
84
+ */
85
+ export async function sealTokenEnvelope(secret: string, envelopeJson: string): Promise<string> {
86
+ return sealTokenBlob(secret, envelopeJson);
87
+ }
88
+
89
+ /**
90
+ * Open a vault path value into its per-user token map.
91
+ *
92
+ * @param secret - Root auth secret
93
+ * @param sealed - Sealed envelope JSON
94
+ */
95
+ export async function unsealTokenEnvelope(
96
+ secret: string,
97
+ sealed: string,
98
+ ): Promise<Record<string, string>> {
99
+ return JSON.parse(await openTokenBlob(secret, sealed)) as Record<string, string>;
100
+ }