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.
- package/package.json +3 -1
- package/site/content/docs/ai/index.mdx +24 -0
- package/site/content/docs/ai/llms-txt.mdx +3 -0
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/console/gates.mdx +46 -8
- package/site/content/docs/console/index.mdx +54 -0
- package/site/content/docs/console/meta.json +1 -0
- package/site/content/docs/elements/gate.mdx +187 -48
- package/site/content/docs/elements/index.mdx +45 -0
- package/site/content/docs/elements/meta.json +1 -1
- package/site/content/docs/get-started/basic-usage.mdx +4 -3
- package/site/content/docs/get-started/index.mdx +33 -0
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/index.mdx +9 -31
- package/site/content/docs/plugins/anonymous.mdx +95 -0
- package/site/content/docs/plugins/compression.mdx +2 -2
- package/site/content/docs/plugins/cors.mdx +2 -2
- package/site/content/docs/plugins/csrf.mdx +2 -2
- package/site/content/docs/plugins/email-otp.mdx +111 -0
- package/site/content/docs/plugins/{security-headers.mdx → headers.mdx} +1 -1
- package/site/content/docs/plugins/index.mdx +69 -0
- package/site/content/docs/plugins/magic-link.mdx +112 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +8 -8
- package/site/content/docs/plugins/meta.json +10 -1
- package/site/content/docs/plugins/passkey.mdx +128 -0
- package/site/content/docs/plugins/phone-number.mdx +111 -0
- package/site/content/docs/plugins/two-factor.mdx +116 -0
- package/site/content/docs/plugins/username.mdx +117 -0
- package/site/content/docs/reference/client.mdx +331 -0
- package/site/content/docs/reference/fx.mdx +20 -5
- package/site/content/docs/reference/index.mdx +45 -0
- package/site/content/docs/reference/meta.json +11 -1
- package/site/content/docs/reference/plugins.mdx +25 -14
- package/src/auth/auth.test.ts +20 -2
- package/src/auth/bindings.ts +439 -0
- package/src/auth/breach-check.ts +112 -0
- package/src/auth/config.ts +288 -0
- package/src/auth/cookies.ts +123 -0
- package/src/auth/gate-auth.test.ts +379 -0
- package/src/auth/identity.ts +190 -0
- package/src/auth/index.ts +117 -1
- package/src/auth/method-context.ts +33 -0
- package/src/auth/operator.ts +27 -1
- package/src/auth/password-policy.test.ts +126 -0
- package/src/auth/password-policy.ts +77 -0
- package/src/auth/plugin.ts +62 -4
- package/src/auth/rate.ts +45 -0
- package/src/auth/schema.ts +260 -0
- package/src/auth/secondary-storage.ts +37 -0
- package/src/auth/sessions.ts +58 -1
- package/src/auth/tables.ts +4 -0
- package/src/auth/verification.ts +78 -0
- package/src/cli/dev.test.ts +3 -3
- package/src/cli/schema.ts +95 -23
- package/src/client/auth.ts +120 -0
- package/src/client-react/index.ts +93 -0
- package/src/compiler/aot.test.ts +2 -1
- package/src/compiler/extract.ts +19 -0
- package/src/compiler/response.ts +16 -2
- package/src/console/server/app.ts +10 -6
- package/src/console/server/auth-rate.test.ts +3 -3
- package/src/console/server/bind.ts +12 -1
- package/src/console/server/channels.test.ts +1 -1
- package/src/console/server/console-gates.ts +14 -0
- package/src/console/server/console.test.ts +6 -6
- package/src/console/server/flows-invoke.test.ts +2 -2
- package/src/console/server/flows.ts +2 -0
- package/src/console/server/gates.ts +8 -1
- package/src/console/server/operator-db.test.ts +4 -4
- package/src/console/server/operator-db.ts +22 -4
- package/src/console/server/security.gate.test.ts +3 -3
- package/src/console/ui/gates/fixture.ts +4 -0
- package/src/console/ui/gates/types.ts +2 -0
- package/src/console/ui/shell/client.ts +1 -0
- package/src/elements/gate/boot.ts +136 -0
- package/src/elements/gate/config.ts +69 -0
- package/src/elements/gate/declare.ts +51 -1
- package/src/elements/gate/runtime.ts +3 -1
- package/src/elements/gate.test.ts +77 -0
- package/src/elements/gate.ts +20 -1
- package/src/elements/index.ts +8 -0
- package/src/index.ts +11 -0
- package/src/kernel/app.ts +253 -32
- package/src/kernel/boot.test.ts +40 -3
- package/src/kernel/boot.ts +8 -0
- package/src/kernel/call.test.ts +46 -2
- package/src/kernel/edge.test.ts +3 -3
- package/src/kernel/flow.test.ts +2 -2
- package/src/kernel/fx.test.ts +1 -0
- package/src/kernel/fx.ts +60 -0
- package/src/kernel/hooks.test.ts +4 -4
- package/src/kernel/index.ts +12 -0
- package/src/kernel/pipeline.test.ts +12 -8
- package/src/kernel/pipeline.ts +23 -4
- package/src/kernel/plugin/decorate.test.ts +3 -3
- package/src/kernel/plugin/scoping.test.ts +3 -3
- package/src/kernel/plugin-elements.test.ts +51 -0
- package/src/kernel/plugin-needs.test.ts +83 -0
- package/src/kernel/plugin-needs.ts +129 -0
- package/src/kernel/plugin.ts +101 -0
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/registry.ts +102 -3
- package/src/manifest/types.ts +2 -0
- package/src/plugins/anonymous.ts +58 -0
- package/src/plugins/auth/shared.ts +121 -0
- package/src/plugins/auth-methods.test.ts +176 -0
- package/src/plugins/compression.test.ts +5 -5
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/cors.test.ts +16 -10
- package/src/plugins/csrf.test.ts +1 -1
- package/src/plugins/email-otp.ts +161 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/ip-allowlist.test.ts +19 -9
- package/src/plugins/magic-link.ts +163 -0
- package/src/plugins/maintenance-mode.test.ts +9 -5
- package/src/plugins/passkey.ts +216 -0
- package/src/plugins/phone-number.ts +149 -0
- package/src/plugins/security-headers.test.ts +14 -14
- package/src/plugins/two-factor.ts +249 -0
- package/src/plugins/username.ts +148 -0
- package/src/runs/runs.test.ts +6 -2
- package/src/runtime/primitives.ts +37 -4
- package/src/runtime/serve.test.ts +3 -2
- package/src/runtime/types.ts +25 -2
- package/src/test/create-test-app.test.ts +1 -1
- package/src/test/create-test-app.ts +4 -1
- 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
|
|
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);
|
package/src/elements/gate.ts
CHANGED
|
@@ -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,
|
package/src/elements/index.ts
CHANGED
|
@@ -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 {
|