okengine 0.4.3 → 0.5.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 (127) hide show
  1. package/package.json +3 -1
  2. package/site/content/docs/ai/index.mdx +24 -0
  3. package/site/content/docs/ai/llms-txt.mdx +3 -0
  4. package/site/content/docs/ai/meta.json +1 -1
  5. package/site/content/docs/console/gates.mdx +46 -8
  6. package/site/content/docs/console/index.mdx +54 -0
  7. package/site/content/docs/console/meta.json +1 -0
  8. package/site/content/docs/elements/gate.mdx +187 -48
  9. package/site/content/docs/elements/index.mdx +45 -0
  10. package/site/content/docs/elements/meta.json +1 -1
  11. package/site/content/docs/get-started/basic-usage.mdx +4 -3
  12. package/site/content/docs/get-started/index.mdx +33 -0
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/index.mdx +9 -31
  15. package/site/content/docs/plugins/anonymous.mdx +95 -0
  16. package/site/content/docs/plugins/compression.mdx +2 -2
  17. package/site/content/docs/plugins/cors.mdx +2 -2
  18. package/site/content/docs/plugins/csrf.mdx +2 -2
  19. package/site/content/docs/plugins/email-otp.mdx +111 -0
  20. package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +1 -1
  21. package/site/content/docs/plugins/index.mdx +69 -0
  22. package/site/content/docs/plugins/magic-link.mdx +112 -0
  23. package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
  24. package/site/content/docs/plugins/meta.json +10 -1
  25. package/site/content/docs/plugins/passkey.mdx +128 -0
  26. package/site/content/docs/plugins/phone-number.mdx +111 -0
  27. package/site/content/docs/plugins/two-factor.mdx +116 -0
  28. package/site/content/docs/plugins/username.mdx +117 -0
  29. package/site/content/docs/reference/client.mdx +331 -0
  30. package/site/content/docs/reference/fx.mdx +20 -5
  31. package/site/content/docs/reference/index.mdx +45 -0
  32. package/site/content/docs/reference/meta.json +11 -1
  33. package/site/content/docs/reference/plugins.mdx +25 -14
  34. package/src/auth/auth.test.ts +20 -2
  35. package/src/auth/bindings.ts +439 -0
  36. package/src/auth/breach-check.ts +112 -0
  37. package/src/auth/config.ts +288 -0
  38. package/src/auth/cookies.ts +123 -0
  39. package/src/auth/gate-auth.test.ts +379 -0
  40. package/src/auth/identity.ts +190 -0
  41. package/src/auth/index.ts +117 -1
  42. package/src/auth/method-context.ts +33 -0
  43. package/src/auth/operator.ts +27 -1
  44. package/src/auth/password-policy.test.ts +126 -0
  45. package/src/auth/password-policy.ts +77 -0
  46. package/src/auth/plugin.ts +62 -4
  47. package/src/auth/rate.ts +45 -0
  48. package/src/auth/schema.ts +260 -0
  49. package/src/auth/secondary-storage.ts +37 -0
  50. package/src/auth/sessions.ts +58 -1
  51. package/src/auth/tables.ts +4 -0
  52. package/src/auth/verification.ts +78 -0
  53. package/src/cli/dev.test.ts +3 -3
  54. package/src/cli/schema.ts +95 -23
  55. package/src/client/auth.ts +120 -0
  56. package/src/client-react/index.ts +93 -0
  57. package/src/compiler/aot.test.ts +2 -1
  58. package/src/compiler/extract.ts +19 -0
  59. package/src/compiler/response.ts +16 -2
  60. package/src/console/server/app.ts +10 -6
  61. package/src/console/server/auth-rate.test.ts +3 -3
  62. package/src/console/server/bind.ts +12 -1
  63. package/src/console/server/channels.test.ts +1 -1
  64. package/src/console/server/console-gates.ts +14 -0
  65. package/src/console/server/console.test.ts +6 -6
  66. package/src/console/server/flows-invoke.test.ts +2 -2
  67. package/src/console/server/flows.ts +2 -0
  68. package/src/console/server/gates.ts +8 -1
  69. package/src/console/server/operator-db.test.ts +4 -4
  70. package/src/console/server/operator-db.ts +22 -4
  71. package/src/console/server/security.gate.test.ts +3 -3
  72. package/src/console/ui/gates/fixture.ts +4 -0
  73. package/src/console/ui/gates/types.ts +2 -0
  74. package/src/console/ui/shell/client.ts +1 -0
  75. package/src/elements/gate/boot.ts +136 -0
  76. package/src/elements/gate/config.ts +69 -0
  77. package/src/elements/gate/declare.ts +51 -1
  78. package/src/elements/gate/runtime.ts +3 -1
  79. package/src/elements/gate.test.ts +77 -0
  80. package/src/elements/gate.ts +20 -1
  81. package/src/elements/index.ts +8 -0
  82. package/src/index.ts +11 -0
  83. package/src/kernel/app.ts +253 -32
  84. package/src/kernel/boot.test.ts +40 -3
  85. package/src/kernel/boot.ts +8 -0
  86. package/src/kernel/call.test.ts +46 -2
  87. package/src/kernel/edge.test.ts +3 -3
  88. package/src/kernel/flow.test.ts +2 -2
  89. package/src/kernel/fx.test.ts +1 -0
  90. package/src/kernel/fx.ts +60 -0
  91. package/src/kernel/hooks.test.ts +4 -4
  92. package/src/kernel/index.ts +12 -0
  93. package/src/kernel/pipeline.test.ts +12 -8
  94. package/src/kernel/pipeline.ts +23 -4
  95. package/src/kernel/plugin/decorate.test.ts +3 -3
  96. package/src/kernel/plugin/scoping.test.ts +3 -3
  97. package/src/kernel/plugin-elements.test.ts +51 -0
  98. package/src/kernel/plugin-needs.test.ts +83 -0
  99. package/src/kernel/plugin-needs.ts +129 -0
  100. package/src/kernel/plugin.ts +101 -0
  101. package/src/kernel/registry-isolation.test.ts +5 -5
  102. package/src/kernel/registry.ts +102 -3
  103. package/src/manifest/types.ts +2 -0
  104. package/src/plugins/anonymous.ts +58 -0
  105. package/src/plugins/auth/shared.ts +121 -0
  106. package/src/plugins/auth-methods.test.ts +176 -0
  107. package/src/plugins/compression.test.ts +5 -5
  108. package/src/plugins/config-source.test.ts +1 -1
  109. package/src/plugins/cors.test.ts +16 -10
  110. package/src/plugins/csrf.test.ts +1 -1
  111. package/src/plugins/email-otp.ts +161 -0
  112. package/src/plugins/index.ts +31 -0
  113. package/src/plugins/ip-allowlist.test.ts +19 -9
  114. package/src/plugins/magic-link.ts +163 -0
  115. package/src/plugins/maintenance-mode.test.ts +9 -5
  116. package/src/plugins/passkey.ts +216 -0
  117. package/src/plugins/phone-number.ts +149 -0
  118. package/src/plugins/security-headers.test.ts +14 -14
  119. package/src/plugins/two-factor.ts +249 -0
  120. package/src/plugins/username.ts +148 -0
  121. package/src/runs/runs.test.ts +6 -2
  122. package/src/runtime/primitives.ts +37 -4
  123. package/src/runtime/serve.test.ts +3 -2
  124. package/src/runtime/types.ts +25 -2
  125. package/src/test/create-test-app.test.ts +1 -1
  126. package/src/test/create-test-app.ts +4 -1
  127. package/src/test/provisions.integration.test.ts +1 -1
@@ -63,6 +63,11 @@ export interface PolicyGateDecl {
63
63
  readonly name: string;
64
64
  readonly check: (ctx: GatePolicyContext) => boolean | Promise<boolean>;
65
65
  readonly description?: string;
66
+ /**
67
+ * Scope strings this policy requires (set by {@link GateNamespace.scope}).
68
+ * Same string is the policy id and the scope checked on `auth.scopes`.
69
+ */
70
+ readonly scopes?: readonly string[];
66
71
  }
67
72
 
68
73
  /** Rate gate declaration. */
@@ -94,6 +99,19 @@ export interface GateNamespace {
94
99
  name: string,
95
100
  checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
96
101
  ): PolicyGateDecl;
102
+ /**
103
+ * Shorthand for a scope check — single source of truth for the scope string.
104
+ *
105
+ * Equivalent to `gate.policy(name, ({ auth }) => auth.scopes.has(name))`.
106
+ *
107
+ * @param name - Scope id (also the policy name / Module:Action when it contains `:`)
108
+ */
109
+ scope(name: string): PolicyGateDecl;
110
+ /**
111
+ * Explicit public sentinel — intentionally unauthenticated HTTP surface.
112
+ * Counts as declared auth posture for boot (see gate boot audit).
113
+ */
114
+ readonly public: PolicyGateDecl;
97
115
  /**
98
116
  * Declare a rate limit (atomic Lua on the kv driver).
99
117
  *
@@ -102,8 +120,11 @@ export interface GateNamespace {
102
120
  rate(options: RateOptions): RateGateDecl;
103
121
  }
104
122
 
123
+ /** Reserved policy name for {@link gate.public}. */
124
+ export const GATE_PUBLIC_NAME = "public";
125
+
105
126
  /**
106
- * Gate element namespace — `gate.policy` · `gate.rate`.
127
+ * Gate element namespace — `gate.policy` · `gate.scope` · `gate.public` · `gate.rate`.
107
128
  */
108
129
  export const gate: GateNamespace = {
109
130
  /**
@@ -116,6 +137,11 @@ export const gate: GateNamespace = {
116
137
  name: string,
117
138
  checkOrOptions: ((ctx: GatePolicyContext) => boolean | Promise<boolean>) | PolicyGateOptions,
118
139
  ): PolicyGateDecl {
140
+ if (name === GATE_PUBLIC_NAME) {
141
+ throw new TypeError(
142
+ 'gate.policy: name "public" is reserved — use gate.public for intentionally unauthenticated surfaces',
143
+ );
144
+ }
119
145
  if (typeof checkOrOptions === "function") {
120
146
  return { kind: "policy", name, check: checkOrOptions };
121
147
  }
@@ -129,6 +155,30 @@ export const gate: GateNamespace = {
129
155
  };
130
156
  },
131
157
 
158
+ /**
159
+ * Shorthand for a scope check — single source of truth for the scope string.
160
+ *
161
+ * @param name - Scope id (also the policy name)
162
+ */
163
+ scope(name: string): PolicyGateDecl {
164
+ if (name === GATE_PUBLIC_NAME) {
165
+ throw new TypeError('gate.scope: name "public" is reserved — use gate.public');
166
+ }
167
+ return {
168
+ kind: "policy",
169
+ name,
170
+ check: ({ auth }) => auth.scopes.has(name),
171
+ scopes: [name],
172
+ };
173
+ },
174
+
175
+ public: {
176
+ kind: "policy",
177
+ name: GATE_PUBLIC_NAME,
178
+ check: () => true,
179
+ description: "Intentionally unauthenticated (public) surface",
180
+ },
181
+
132
182
  /**
133
183
  * Declare a rate limit (atomic Lua on the kv driver).
134
184
  *
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { parseDurationMs } from "../clock/duration.ts";
6
- import type { GateDecl, GatePolicyContext, RateGateDecl } from "./declare.ts";
6
+ import { gate, type GateDecl, type GatePolicyContext, type RateGateDecl } from "./declare.ts";
7
7
  import { takeRate } from "./strategies.ts";
8
8
 
9
9
  /** KV surface required for rate gates. */
@@ -58,6 +58,8 @@ export interface GateRuntime {
58
58
  */
59
59
  export function createGateRuntime(options: CreateGateRuntimeOptions = {}): GateRuntime {
60
60
  const map = new Map<string, GateDecl>();
61
+ // Built-in sentinel — `.gate(gate.public)` works without listing it in `oke({ gates })`.
62
+ map.set(gate.public.name, gate.public);
61
63
  for (const g of options.gates ?? []) {
62
64
  map.set(g.name, g);
63
65
  }
@@ -11,10 +11,12 @@ import { createRedisFakeClient, redisDriver } from "../drivers/redis.ts";
11
11
  import type { Manifest } from "../manifest/types.ts";
12
12
  import {
13
13
  ALL_RATE_STRATEGIES,
14
+ assertHttpGatePosture,
14
15
  createGateRuntime,
15
16
  deriveModuleActions,
16
17
  formatGatesList,
17
18
  gate,
19
+ GateBootError,
18
20
  takeRate,
19
21
  } from "./gate.ts";
20
22
 
@@ -35,6 +37,37 @@ describe("gate declaration", () => {
35
37
  expect(fair.strategy).toBe("sliding-window-counter");
36
38
  });
37
39
 
40
+ test("scope is sugar over policy with auth.scopes.has(name)", async () => {
41
+ const canBook = gate.scope("booking:create");
42
+ expect(canBook.kind).toBe("policy");
43
+ expect(canBook.name).toBe("booking:create");
44
+ expect(canBook.scopes).toEqual(["booking:create"]);
45
+ expect(
46
+ await canBook.check({
47
+ auth: { userId: "u1", scopes: new Set(["booking:create"]), verified: true },
48
+ operator: { id: null },
49
+ }),
50
+ ).toBe(true);
51
+ expect(
52
+ await canBook.check({
53
+ auth: { userId: "u1", scopes: new Set(), verified: true },
54
+ operator: { id: null },
55
+ }),
56
+ ).toBe(false);
57
+ });
58
+
59
+ test("public sentinel always allows and reserves the name", async () => {
60
+ expect(gate.public.name).toBe("public");
61
+ expect(
62
+ await gate.public.check({
63
+ auth: { userId: null, scopes: new Set() },
64
+ operator: { id: null },
65
+ }),
66
+ ).toBe(true);
67
+ expect(() => gate.policy("public", () => true)).toThrow(/reserved/);
68
+ expect(() => gate.scope("public")).toThrow(/reserved/);
69
+ });
70
+
38
71
  test("defaults strategy to sliding-window-counter", () => {
39
72
  const r = gate.rate({ max: 10, per: "1s" });
40
73
  expect(r.strategy).toBe("sliding-window-counter");
@@ -117,7 +150,51 @@ describe("five rate strategies — concurrency", () => {
117
150
  });
118
151
  });
119
152
 
153
+ describe("gate boot posture", () => {
154
+ const unguarded = [
155
+ {
156
+ trigger: { kind: "http" as const, method: "GET", path: "/health", gates: [] as const },
157
+ flow: { name: "health.ping" },
158
+ },
159
+ ];
160
+
161
+ test("GateBootError lists every unguarded HTTP trigger", () => {
162
+ expect(() =>
163
+ assertHttpGatePosture([
164
+ ...unguarded,
165
+ {
166
+ trigger: { kind: "http", method: "POST", path: "/x", gates: [gate.public] },
167
+ flow: { name: "x.public" },
168
+ },
169
+ ]),
170
+ ).toThrow(GateBootError);
171
+ });
172
+
173
+ test("unguardedHttp allow skips audit only when env is test", () => {
174
+ expect(() =>
175
+ assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env: "test" }),
176
+ ).not.toThrow();
177
+ });
178
+
179
+ test("unguardedHttp allow has no effect outside env === test", () => {
180
+ for (const env of ["local", "prod", "docker"] as const) {
181
+ expect(() => assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env })).toThrow(
182
+ GateBootError,
183
+ );
184
+ }
185
+ });
186
+ });
187
+
120
188
  describe("gate runtime", () => {
189
+ test("public sentinel is always registered", async () => {
190
+ const rt = createGateRuntime();
191
+ const ok = await rt.allow(["public"], {
192
+ auth: { userId: null, scopes: new Set() },
193
+ operator: { id: null },
194
+ });
195
+ expect(ok).toBe(true);
196
+ });
197
+
121
198
  test("policy then rate — evaluation order", async () => {
122
199
  const kv = await memoryKvDriver.open({ name: "gate-rt" });
123
200
  const member = gate.policy("member", ({ auth }) => !!auth.verified);
@@ -8,7 +8,7 @@
8
8
  * @module
9
9
  */
10
10
 
11
- export { gate } from "./gate/declare.ts";
11
+ export { gate, GATE_PUBLIC_NAME } from "./gate/declare.ts";
12
12
  export type {
13
13
  GateDecl,
14
14
  GatePolicyContext,
@@ -18,6 +18,25 @@ export type {
18
18
  RateOptions,
19
19
  } from "./gate/declare.ts";
20
20
 
21
+ export {
22
+ resolveGateConfig,
23
+ type GateOptions,
24
+ type GateRateLimitOptions,
25
+ type ResolvedGateConfig,
26
+ type ResolveGateConfigOptions,
27
+ } from "./gate/config.ts";
28
+
29
+ export {
30
+ GateBootError,
31
+ assertHttpGatePosture,
32
+ collectUnguardedHttpGaps,
33
+ hasHttpGatePosture,
34
+ isPublicGateRef,
35
+ unguardedHttpAllowActive,
36
+ type AssertHttpGatePostureOptions,
37
+ type GatePostureGap,
38
+ } from "./gate/boot.ts";
39
+
21
40
  export {
22
41
  createGateRuntime,
23
42
  type CreateGateRuntimeOptions,
@@ -62,6 +62,13 @@ export type { ClockDecl, ClockOptions, ClockRuntime, TimeTravel, DurableResult }
62
62
 
63
63
  export {
64
64
  gate,
65
+ GATE_PUBLIC_NAME,
66
+ GateBootError,
67
+ assertHttpGatePosture,
68
+ collectUnguardedHttpGaps,
69
+ hasHttpGatePosture,
70
+ isPublicGateRef,
71
+ unguardedHttpAllowActive,
65
72
  createGateRuntime,
66
73
  takeRate,
67
74
  deriveModuleActions,
@@ -72,6 +79,7 @@ export {
72
79
  export type {
73
80
  GateDecl,
74
81
  GatePolicyContext,
82
+ GatePostureGap,
75
83
  GateRuntime,
76
84
  RateOptions,
77
85
  RateTakeResult,
package/src/index.ts CHANGED
@@ -38,12 +38,16 @@ export {
38
38
  isRedacted,
39
39
  maskRedactedDeep,
40
40
  REDACTED_PLACEHOLDER,
41
+ PluginNeedsError,
42
+ assertPluginNeeds,
43
+ freezePrincipal,
41
44
  type OkeApp,
42
45
  type OkeOptions,
43
46
  type FlowDef,
44
47
  type FlowFailure,
45
48
  type FlowErrorValue,
46
49
  type Fx,
50
+ type FxPrincipal,
47
51
  type Binding,
48
52
  type PluginDef,
49
53
  type PluginCapabilities,
@@ -94,13 +98,20 @@ export {
94
98
 
95
99
  export {
96
100
  gate,
101
+ GATE_PUBLIC_NAME,
102
+ GateBootError,
103
+ assertHttpGatePosture,
97
104
  createGateRuntime,
105
+ resolveGateConfig,
98
106
  takeRate,
99
107
  deriveModuleActions,
100
108
  ALL_RATE_STRATEGIES,
101
109
  type GateDecl,
110
+ type GateOptions,
111
+ type GatePostureGap,
102
112
  type GateRuntime,
103
113
  type RateOptions,
114
+ type ResolvedGateConfig,
104
115
  } from "./elements/gate.ts";
105
116
 
106
117
  export {