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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"exports": {
|
|
27
27
|
".": "./src/index.ts",
|
|
28
28
|
"./client": "./src/client/index.ts",
|
|
29
|
+
"./client/auth": "./src/client/auth.ts",
|
|
30
|
+
"./client-react": "./src/client-react/index.ts",
|
|
29
31
|
"./test": "./src/test/index.ts",
|
|
30
32
|
"./config": "./src/config/index.ts",
|
|
31
33
|
"./auth": "./src/auth/index.ts",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "AI Resources"
|
|
3
|
+
description: "Machine-facing surfaces — MCP on :6535/:6536, agent skills, and /llms.txt."
|
|
4
|
+
icon: "Bot"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Resources for AI agents that read or operate an OKE app — not the `ai` element itself.
|
|
9
|
+
|
|
10
|
+
## Pages
|
|
11
|
+
|
|
12
|
+
<Cards>
|
|
13
|
+
<Card title="MCP" description="Runtime MCP on :6535; docs MCP on :6536." href="/docs/ai/mcp" />
|
|
14
|
+
<Card
|
|
15
|
+
title="Skills"
|
|
16
|
+
description="AGENTS.md contract and installable skills."
|
|
17
|
+
href="/docs/ai/skills"
|
|
18
|
+
/>
|
|
19
|
+
<Card
|
|
20
|
+
title="llms.txt"
|
|
21
|
+
description="Machine-readable docs index and pages."
|
|
22
|
+
href="/docs/ai/llms-txt"
|
|
23
|
+
/>
|
|
24
|
+
</Cards>
|
|
@@ -31,6 +31,9 @@ curl https://oke.omqkhafi.dev/llms.mdx/docs/elements/vault
|
|
|
31
31
|
only when breadth matters more than tokens; per-page `/llms.mdx` is the precise instrument.
|
|
32
32
|
</Callout>
|
|
33
33
|
|
|
34
|
+
The homepage **Onboard AI** button copies a one-line prompt that points an agent at
|
|
35
|
+
`/llms.txt` plus the repo-root `AGENTS.md` — project-wide context, not a single docs page.
|
|
36
|
+
|
|
34
37
|
## How it pairs with the docs MCP
|
|
35
38
|
|
|
36
39
|
These endpoints serve the _published_ docs of the framework. During `oke dev`, the [docs MCP server](/docs/ai/mcp) also runs on :6536 (`oke.docs.search` / `oke.docs.get`), answering from the same content over the MCP protocol — pick the plain HTTP endpoints for one-shot context, the MCP tools when the agent is already connected as a client.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Gates"
|
|
3
|
-
description: "Permission
|
|
3
|
+
description: "Permission inquiry, rate counters, and continuous audit of unguarded vs explicitly public flows."
|
|
4
4
|
icon: "Shield"
|
|
5
5
|
source: "docs/spec/console.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The Gates panel answers **who may do what** without dumping a giant roles×permissions matrix as
|
|
9
|
+
the home screen. Inquire from a principal or from a flow; the simulator shows the chain in
|
|
10
|
+
evaluation order.
|
|
9
11
|
|
|
10
|
-
<Callout title="
|
|
12
|
+
<Callout title="The one rule">
|
|
11
13
|
Refuse the giant roles×permissions matrix as the entry point — inquire from principal or from
|
|
12
14
|
flow.
|
|
13
15
|
</Callout>
|
|
@@ -15,10 +17,10 @@ Answers: **permission matrix, rate counters, MFA map**
|
|
|
15
17
|
## What this panel shows
|
|
16
18
|
|
|
17
19
|
<Cards>
|
|
18
|
-
<Card title="Simulator" description="Gate chain in evaluation order — where a
|
|
20
|
+
<Card title="Simulator" description="Gate chain in evaluation order — where a denial stopped." />
|
|
19
21
|
<Card
|
|
20
|
-
title="Unguarded
|
|
21
|
-
description="
|
|
22
|
+
title="Unguarded vs public"
|
|
23
|
+
description="Empty chain (missing posture) vs gate.public (intentional)."
|
|
22
24
|
/>
|
|
23
25
|
<Card title="Deploy diff" description="Permission widening caught from Manifest Diff." />
|
|
24
26
|
<Card
|
|
@@ -27,9 +29,45 @@ Answers: **permission matrix, rate counters, MFA map**
|
|
|
27
29
|
/>
|
|
28
30
|
</Cards>
|
|
29
31
|
|
|
32
|
+
## Unguarded vs explicit public
|
|
33
|
+
|
|
34
|
+
| Flag | Meaning |
|
|
35
|
+
| --------------- | ---------------------------------------------------------------------------- |
|
|
36
|
+
| Unguarded | User-plane flow with an **empty** gate chain — missing auth posture |
|
|
37
|
+
| Explicit public | Chain includes the reserved `public` sentinel (`gate.public` on the trigger) |
|
|
38
|
+
|
|
39
|
+
App boot fails on unguarded HTTP triggers (`GateBootError`) unless `env === "test"` and
|
|
40
|
+
`gate: { unguardedHttp: "allow" }`. Prefer fixing the flow: attach a policy, or attach
|
|
41
|
+
`gate.public` when the surface is intentionally open.
|
|
42
|
+
|
|
43
|
+
App authors configure Gate under `oke({ gate: { policies, auth, unguardedHttp } })` — see
|
|
44
|
+
[Gate](/docs/elements/gate). Builtin `/auth/*` Flows from `gate.auth` are audited the same way.
|
|
45
|
+
|
|
30
46
|
## Catalog
|
|
31
47
|
|
|
48
|
+
Policies, rates, and `/auth/*` Bindings from the Manifest — inquire and audit, not a live
|
|
49
|
+
rate editor. Rate `overridable` is a catalog flag; schedule edits live under
|
|
50
|
+
[Clock](/docs/console/clock).
|
|
51
|
+
|
|
32
52
|
<Cards>
|
|
33
|
-
<Card title="Dev" description="full" />
|
|
34
|
-
<Card title="Prod" description="
|
|
53
|
+
<Card title="Dev" description="full catalog + simulator" />
|
|
54
|
+
<Card title="Prod" description="same inquire surface; no silent ungated HTTP" />
|
|
55
|
+
</Cards>
|
|
56
|
+
|
|
57
|
+
## Learn more
|
|
58
|
+
|
|
59
|
+
- [Gate](/docs/elements/gate) — policies, rate limits, `gate.auth`, posture
|
|
60
|
+
- [Plugins](/docs/plugins) — plugged sign-in method plugins
|
|
61
|
+
- [Access](/docs/console/access) — roles and API keys
|
|
62
|
+
|
|
63
|
+
## Next
|
|
64
|
+
|
|
65
|
+
<Cards>
|
|
66
|
+
<Card title="Gate" description="Declare policies and auth." href="/docs/elements/gate" />
|
|
67
|
+
<Card title="Access" description="Roles and API keys." href="/docs/console/access" />
|
|
68
|
+
<Card
|
|
69
|
+
title="Overview"
|
|
70
|
+
description="Console panels from the Manifest."
|
|
71
|
+
href="/docs/console/overview"
|
|
72
|
+
/>
|
|
35
73
|
</Cards>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Console"
|
|
3
|
+
description: "Manifest-derived operator panels on :6533 — health, elements, traces, access, and more."
|
|
4
|
+
icon: "LayoutDashboard"
|
|
5
|
+
source: "docs/spec/console.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The Console is derived from your Manifest. Each panel answers one operator question.
|
|
9
|
+
|
|
10
|
+
## Pages
|
|
11
|
+
|
|
12
|
+
<Cards>
|
|
13
|
+
<Card
|
|
14
|
+
title="Overview"
|
|
15
|
+
description="Is the system healthy right now?"
|
|
16
|
+
href="/docs/console/overview"
|
|
17
|
+
/>
|
|
18
|
+
<Card
|
|
19
|
+
title="Flows"
|
|
20
|
+
description="What exists; call it; read its contract."
|
|
21
|
+
href="/docs/console/flows"
|
|
22
|
+
/>
|
|
23
|
+
<Card title="Signals" description="Queue depth, in-flight, DLQ." href="/docs/console/signals" />
|
|
24
|
+
<Card title="Store" description="Browse sql/kv/files/index." href="/docs/console/store" />
|
|
25
|
+
<Card title="Clock" description="Crons, sleeps, journal." href="/docs/console/clock" />
|
|
26
|
+
<Card
|
|
27
|
+
title="Gates"
|
|
28
|
+
description="Permission matrix and rate counters."
|
|
29
|
+
href="/docs/console/gates"
|
|
30
|
+
/>
|
|
31
|
+
<Card title="Vault" description="Secret contracts and rotation." href="/docs/console/vault" />
|
|
32
|
+
<Card
|
|
33
|
+
title="Channels"
|
|
34
|
+
description="Templates, receipts, opt-outs."
|
|
35
|
+
href="/docs/console/channels"
|
|
36
|
+
/>
|
|
37
|
+
<Card title="AI" description="Prompts, evals, cost, agents." href="/docs/console/ai" />
|
|
38
|
+
<Card title="Architecture" description="How it all connects." href="/docs/console/architecture" />
|
|
39
|
+
<Card title="Traces" description="One timeline across effects." href="/docs/console/traces" />
|
|
40
|
+
<Card title="Runs" description="Wide events per execution." href="/docs/console/runs" />
|
|
41
|
+
<Card
|
|
42
|
+
title="Manifest Diff"
|
|
43
|
+
description="Blast radius of a deploy."
|
|
44
|
+
href="/docs/console/manifest-diff"
|
|
45
|
+
/>
|
|
46
|
+
<Card title="Access" description="Identities, roles, API keys." href="/docs/console/access" />
|
|
47
|
+
<Card title="Plugins" description="Installed plugins and panels." href="/docs/console/plugins" />
|
|
48
|
+
<Card title="Privacy" description="Where PII lives (conditional)." href="/docs/console/privacy" />
|
|
49
|
+
<Card
|
|
50
|
+
title="Tenancy"
|
|
51
|
+
description="Per-tenant usage (conditional)."
|
|
52
|
+
href="/docs/console/tenancy"
|
|
53
|
+
/>
|
|
54
|
+
</Cards>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Gate"
|
|
3
|
-
description: "Permission to act —
|
|
3
|
+
description: "Permission to act — policies, rate limits, and built-in auth on the HTTP trigger, evaluated before any effect runs."
|
|
4
4
|
icon: "ShieldCheck"
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Gate
|
|
8
|
+
Gate answers **"may this happen?"** before it happens: is this a verified member? has this IP
|
|
9
|
+
burned its minute quota? Attach gates to the HTTP trigger — the pipeline checks them before a
|
|
10
|
+
store write, emit, or channel send runs.
|
|
9
11
|
|
|
10
12
|
<Callout title="The one rule">
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
Every HTTP trigger declares posture: attach a real gate, or attach `gate.public`. Omitting both
|
|
14
|
+
fails boot. Denial is a typed error value — never a thrown exception mid-`do`.
|
|
13
15
|
</Callout>
|
|
14
16
|
|
|
15
17
|
## Quick start
|
|
@@ -17,50 +19,80 @@ Gate is how your app answers **"may this happen?"** before it happens: is this r
|
|
|
17
19
|
<Steps>
|
|
18
20
|
|
|
19
21
|
<Step>
|
|
20
|
-
### Declare
|
|
22
|
+
### Declare policies and register the Gate bag
|
|
21
23
|
|
|
22
|
-
Policies are named predicates; rate limits are declarative budgets.
|
|
24
|
+
Policies are named predicates; rate limits are declarative budgets. Put the ones you reuse in
|
|
25
|
+
`oke({ gate: { policies } })`:
|
|
23
26
|
|
|
24
27
|
```typescript title="src/gates.ts"
|
|
25
28
|
import { gate } from "okengine";
|
|
26
29
|
|
|
27
|
-
export const member = gate.policy("member", ({ auth }) => !!auth
|
|
30
|
+
export const member = gate.policy("member", ({ auth }) => !!auth.verified);
|
|
31
|
+
|
|
32
|
+
export const canBook = gate.scope("booking:create");
|
|
28
33
|
|
|
29
34
|
export const fair = gate.rate({
|
|
30
|
-
strategy: "sliding-window-counter", // the default — near-exact, no boundary bursts
|
|
31
35
|
max: 60,
|
|
32
36
|
per: "1m",
|
|
33
37
|
keyBy: "ip",
|
|
34
38
|
});
|
|
35
39
|
```
|
|
36
40
|
|
|
41
|
+
`gate.scope(name)` is sugar for
|
|
42
|
+
`gate.policy(name, ({ auth }) => auth.scopes.has(name))` — one string is both the policy id and
|
|
43
|
+
the scope checked.
|
|
44
|
+
|
|
45
|
+
```typescript title="src/app.ts"
|
|
46
|
+
import { oke } from "okengine";
|
|
47
|
+
import { member, canBook, fair } from "./gates";
|
|
48
|
+
|
|
49
|
+
export const app = oke({
|
|
50
|
+
name: "shop",
|
|
51
|
+
env: "local",
|
|
52
|
+
gate: { policies: [member, canBook, fair] },
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
37
56
|
</Step>
|
|
38
57
|
|
|
39
58
|
<Step>
|
|
40
59
|
### Attach them to triggers
|
|
41
60
|
|
|
42
|
-
`.gate(...)` composes
|
|
61
|
+
`.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly:
|
|
43
62
|
|
|
44
63
|
```typescript title="src/flows/links/shorten.ts"
|
|
64
|
+
import { on, flow, http, gate } from "okengine";
|
|
65
|
+
import { member, canBook, fair } from "../../gates";
|
|
66
|
+
|
|
67
|
+
export const health = on(
|
|
68
|
+
http.get("/health").gate(gate.public),
|
|
69
|
+
flow({
|
|
70
|
+
out: HealthOut,
|
|
71
|
+
do: () => ({ ok: true as const }),
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
45
75
|
export const shorten = on(
|
|
46
|
-
http.post("/links").gate(member, fair),
|
|
76
|
+
http.post("/links").gate(member, canBook, fair),
|
|
47
77
|
flow({
|
|
48
78
|
in: NewLink,
|
|
49
79
|
out: LinkCode,
|
|
50
80
|
errors: { Taken },
|
|
51
81
|
do: async ({ url, code }, fx) => {
|
|
52
|
-
/* runs only if
|
|
82
|
+
/* runs only if every gate passed */
|
|
53
83
|
},
|
|
54
84
|
}),
|
|
55
85
|
);
|
|
56
86
|
```
|
|
57
87
|
|
|
88
|
+
Do **not** put `gate.public` in `gate.policies` — attach it on the trigger only.
|
|
89
|
+
|
|
58
90
|
</Step>
|
|
59
91
|
|
|
60
92
|
<Step>
|
|
61
|
-
###
|
|
93
|
+
### See typed denials
|
|
62
94
|
|
|
63
|
-
A denied request never reaches `do`. It returns one of three typed failures
|
|
95
|
+
A denied request never reaches `do`. It returns one of three typed failures:
|
|
64
96
|
|
|
65
97
|
| Gate result | Typed failure |
|
|
66
98
|
| -------------------------------- | ------------------------------------- |
|
|
@@ -74,32 +106,38 @@ A denied request never reaches `do`. It returns one of three typed failures, lik
|
|
|
74
106
|
|
|
75
107
|
<GatePipeline />
|
|
76
108
|
|
|
77
|
-
##
|
|
109
|
+
## `oke({ gate })` bag
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Meaning |
|
|
112
|
+
| --------------- | ----------------------- | -------- | --------------------------------------------------------------- |
|
|
113
|
+
| `policies` | `GateDecl[]` | `[]` | Named policy / rate decls for the Gate runtime |
|
|
114
|
+
| `auth` | `GateAuthOptions` | — | Builtin hybrid sessions + `/auth/*` Flows (see below) |
|
|
115
|
+
| `rateLimit` | `{ enabled?: boolean }` | see note | When auth is on, defaults `true` — stricter rates on auth paths |
|
|
116
|
+
| `unguardedHttp` | `"deny"` \| `"allow"` | `"deny"` | `"allow"` skips posture audit **only** when `env === "test"` |
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
| ------------------------------------- | -------------------------------------- | ---------------------------------- |
|
|
81
|
-
| `gate.policy(name, check \| options)` | Is this principal allowed? (ABAC) | auth / operator / request metadata |
|
|
82
|
-
| `gate.rate(options)` | Is there budget left for this subject? | an atomic counter on the kv driver |
|
|
118
|
+
## Declarations
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
(
|
|
120
|
+
| Declaration | Question it answers | Evaluated against |
|
|
121
|
+
| ------------------------------------- | ------------------------------------------- | ---------------------------------- |
|
|
122
|
+
| `gate.policy(name, check \| options)` | Is this principal allowed? (ABAC) | auth / operator / request metadata |
|
|
123
|
+
| `gate.scope(name)` | Does `auth.scopes` contain this exact name? | `auth.scopes` |
|
|
124
|
+
| `gate.public` | Intentionally unauthenticated surface | always allows |
|
|
125
|
+
| `gate.rate(options)` | Is there budget left for this subject? | an atomic counter on the kv driver |
|
|
87
126
|
|
|
88
|
-
|
|
127
|
+
Pass `{ check, description }` on `gate.policy` when you want a human title in the Console (falls
|
|
128
|
+
back to the policy name). The name `"public"` is reserved — use `gate.public`, not
|
|
129
|
+
`gate.policy("public", …)`.
|
|
89
130
|
|
|
90
|
-
|
|
131
|
+
### Policy context
|
|
91
132
|
|
|
92
133
|
| Field | Contents |
|
|
93
134
|
| ---------- | -------------------------------------------------------- |
|
|
94
135
|
| `auth` | User principal: `userId`, `scopes` (a `Set`), `verified` |
|
|
95
136
|
| `operator` | Operator principal (Console plane): `id` |
|
|
96
|
-
| `meta` | Request metadata for keying
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
export const admin = gate.policy("admin", ({ auth }) => auth.scopes.has("admin"));
|
|
100
|
-
```
|
|
137
|
+
| `meta` | Request metadata for keying: `ip`, `userId`, … |
|
|
101
138
|
|
|
102
|
-
A policy name containing `:` is also a `Module:Action` permission
|
|
139
|
+
A policy name containing `:` is also a `Module:Action` permission for Console role reviews.
|
|
140
|
+
Fine-grained authorization (`gate.scope`, roles) stays opt-in — attach it only where you need it.
|
|
103
141
|
|
|
104
142
|
### Rate options
|
|
105
143
|
|
|
@@ -109,7 +147,7 @@ A policy name containing `:` is also a `Module:Action` permission — the same d
|
|
|
109
147
|
| `per` | string | — (**required**) | Window / refill period (`"1m"`, `"60s"`, …) |
|
|
110
148
|
| `keyBy` | string | — | Subject dimension (`"ip"`, `"user"`, …) |
|
|
111
149
|
| `strategy` | RateStrategy | `"sliding-window-counter"` | Algorithm (below) |
|
|
112
|
-
| `overridable` | boolean | `false` |
|
|
150
|
+
| `overridable` | boolean | `false` | Catalog flag in Console (rate edit UI not shipped) |
|
|
113
151
|
| `description` | string | — | Human title in the Console (falls back to the rate name) |
|
|
114
152
|
|
|
115
153
|
| Strategy | Behavior |
|
|
@@ -120,37 +158,139 @@ A policy name containing `:` is also a `Module:Action` permission — the same d
|
|
|
120
158
|
| `token-bucket` | Steady refill; permits saved-up bursts |
|
|
121
159
|
| `leaky-bucket` | Smooths output to a constant rate |
|
|
122
160
|
|
|
123
|
-
All five run as atomic Lua on the kv driver — correct under concurrency, identical on the memory
|
|
161
|
+
All five run as atomic Lua on the kv driver — correct under concurrency, identical on the memory
|
|
162
|
+
driver in tests.
|
|
163
|
+
|
|
164
|
+
## Auth posture at boot
|
|
165
|
+
|
|
166
|
+
Every HTTP trigger must carry a gate or `gate.public`. Omitting both fails boot with
|
|
167
|
+
`GateBootError` — message starts with `gate boot failed —` and lists every gap.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
on(http.get("/health").gate(gate.public), flow({/* … */}));
|
|
171
|
+
on(http.post("/bookings").gate(member, canBook), flow({/* … */}));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Consequence:** first `fetch` / `execute` auto-boots (`autoBoot` defaults to `true`).
|
|
175
|
+
`oke({ autoBoot: false })` is a unit-test escape hatch only. A rate-only chain is declared
|
|
176
|
+
posture but does **not** authenticate.
|
|
124
177
|
|
|
125
|
-
|
|
178
|
+
`gate: { unguardedHttp: "allow" }` skips the audit **only** when `env === "test"`.
|
|
179
|
+
In `local` / `prod` / `docker` migrate with per-trigger `gate.public`.
|
|
126
180
|
|
|
127
|
-
|
|
181
|
+
## Built-in auth (`gate.auth`)
|
|
128
182
|
|
|
129
|
-
|
|
183
|
+
Enable hybrid sessions (Bearer access + revocable refresh). Auth HTTP surfaces are ordinary
|
|
184
|
+
Bindings under `/auth` — same posture audit and pipeline as your app Flows:
|
|
130
185
|
|
|
131
|
-
|
|
186
|
+
```typescript title="src/app.ts"
|
|
187
|
+
import { oke } from "okengine";
|
|
188
|
+
import { member, canBook } from "./gates";
|
|
189
|
+
|
|
190
|
+
export const app = oke({
|
|
191
|
+
name: "shop",
|
|
192
|
+
env: "local",
|
|
193
|
+
gate: {
|
|
194
|
+
auth: {
|
|
195
|
+
emailAndPassword: { enabled: true },
|
|
196
|
+
session: { freshAgeMs: 24 * 60 * 60_000 },
|
|
197
|
+
},
|
|
198
|
+
policies: [member, canBook],
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
| Surface | When |
|
|
204
|
+
| -------------------------- | -------------------------------------- |
|
|
205
|
+
| `POST /auth/sign-up/email` | `emailAndPassword.enabled` |
|
|
206
|
+
| `POST /auth/sign-in/email` | `emailAndPassword.enabled` |
|
|
207
|
+
| `POST /auth/refresh` | when `gate.auth` is set |
|
|
208
|
+
| `POST /auth/revoke` | when `gate.auth` is set |
|
|
209
|
+
| `GET /auth/me` | Bearer session (`auth.session` policy) |
|
|
210
|
+
|
|
211
|
+
| Auth option | Default | Meaning |
|
|
212
|
+
| ------------------------------------------- | --------- | ------------------------------------------------------------------------------ |
|
|
213
|
+
| `secret` | minted\* | HMAC for access tokens (\*`prod` requires you set it) |
|
|
214
|
+
| `basePath` | `"/auth"` | HTTP prefix for auth Flows |
|
|
215
|
+
| `http` | `true` | `false` = secret/tables only (no `/auth/*` Bindings) |
|
|
216
|
+
| `audience` | `oke-app` | Audience stamped on access tokens |
|
|
217
|
+
| `emailAndPassword.enabled` | `false` | Sign-up / sign-in email Flows |
|
|
218
|
+
| `emailAndPassword.requireEmailVerification` | `false` | Block sign-in until email is verified |
|
|
219
|
+
| `passwordPolicy` | secure\* | `\*` minLength 12, letter + number; sign-up → `AuthFailed` / `password_policy` |
|
|
220
|
+
| `breachCheck` | unset | `(password) => Promise<boolean>` — `true` rejects as `password_breached` |
|
|
221
|
+
| `session.freshAgeMs` | 24h | Window for step-up `auth.fresh` policy |
|
|
222
|
+
| `session.accessTtlMs` / `refreshTtlMs` | 14m / 30d | Access / refresh lifetimes |
|
|
223
|
+
| `session.idleTtlMs` | unset | Opt-in: reject refresh when idle too long |
|
|
224
|
+
| `session.absoluteTtlMs` | unset | Opt-in: hard cap from session creation |
|
|
225
|
+
| `session.singleSessionPerUser` | `false` | Opt-in: issuing a session revokes other families for that user |
|
|
226
|
+
| `cookies.enabled` | `false` | Opt-in HttpOnly cookie mirror (Bearer stays default) |
|
|
227
|
+
| `secondaryStorage.enabled` | `false` | Hot auth data in `store.kv` when configured |
|
|
228
|
+
| `user` / `session` / … | defaults | `modelName` / `fields` / `additionalFields` |
|
|
229
|
+
|
|
230
|
+
HIBP via `createHibpBreachCheck` from `okengine/auth` (k-anonymity range API; requires
|
|
231
|
+
`userAgent`; `onError` defaults to `"reject"` — fail closed while the check is enabled):
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { createHibpBreachCheck } from "okengine/auth";
|
|
235
|
+
|
|
236
|
+
gate: {
|
|
237
|
+
auth: {
|
|
238
|
+
emailAndPassword: { enabled: true },
|
|
239
|
+
breachCheck: createHibpBreachCheck({ userAgent: "shop/1.0" }),
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Customize tables, then run `oke schema generate` (supports `--check` in CI). Extra sign-in
|
|
245
|
+
shapes: [Plugins](/docs/plugins) (username, magic link, OTP, TOTP, passkeys). Call auth from
|
|
246
|
+
[createClient](/docs/reference/client) — one client, helpers in `okengine/client/auth`.
|
|
247
|
+
|
|
248
|
+
## Identity and decisions
|
|
249
|
+
|
|
250
|
+
Gates decide on principals that already exist. `gate.auth` issues hybrid sessions onto
|
|
251
|
+
`fx.auth` (Console: `fx.operator`). Audit across `fx.call` uses read-only `fx.principal`
|
|
252
|
+
(see [fx](/docs/reference/fx)).
|
|
253
|
+
|
|
254
|
+
Pass **and** deny land on the run's `gates` dimension. Console (`:6533` → Gates) shows the
|
|
255
|
+
chain. Rate `overridable` is a catalog flag only — Clock schedules are what the Console can
|
|
256
|
+
edit live today.
|
|
132
257
|
|
|
133
258
|
## Troubleshooting
|
|
134
259
|
|
|
135
260
|
<Accordions>
|
|
261
|
+
<Accordion title="Boot: gate boot failed — N HTTP trigger(s) missing auth posture">
|
|
262
|
+
|
|
263
|
+
Empty `.gate(...)` chain. Attach a policy/rate gate, or `.gate(gate.public)`. First `fetch`
|
|
264
|
+
auto-boots — do not rely on `gate.unguardedHttp: "allow"` outside `env: "test"`.
|
|
265
|
+
|
|
266
|
+
</Accordion>
|
|
136
267
|
<Accordion title="Can a policy read the request body?">
|
|
137
268
|
|
|
138
|
-
No
|
|
269
|
+
No. Gates run before the flow, so they see only `auth`, `operator`, and `meta`. Body-dependent
|
|
270
|
+
decisions belong inside `do` as typed `errors`.
|
|
139
271
|
|
|
140
272
|
</Accordion>
|
|
141
273
|
<Accordion title="Logged-in users still get Forbidden">
|
|
142
274
|
|
|
143
|
-
`Forbidden` means
|
|
275
|
+
`Forbidden` means authenticated but a policy said no. Check `verified` or a missing scope —
|
|
276
|
+
the failure includes the gate name and reason.
|
|
144
277
|
|
|
145
278
|
</Accordion>
|
|
146
|
-
<Accordion title="
|
|
279
|
+
<Accordion title="Prod boot: gate.auth: secret is required">
|
|
147
280
|
|
|
148
|
-
`
|
|
281
|
+
Set `gate.auth.secret` (or provide it before boot). Non-prod may mint a development secret.
|
|
149
282
|
|
|
150
283
|
</Accordion>
|
|
151
|
-
<Accordion title="
|
|
284
|
+
<Accordion title="Sign-up returns AuthFailed password_policy">
|
|
152
285
|
|
|
153
|
-
|
|
286
|
+
Default policy requires length ≥ 12 plus a letter and a number. Short passwords like `"123"`
|
|
287
|
+
are rejected — not accepted silently. Tighten or loosen via `gate.auth.passwordPolicy`.
|
|
288
|
+
|
|
289
|
+
</Accordion>
|
|
290
|
+
<Accordion title="Which keyBy should I use?">
|
|
291
|
+
|
|
292
|
+
`"ip"` for public surfaces (sign-up, password reset), `"user"` for authenticated quotas.
|
|
293
|
+
Keying an authenticated endpoint by IP punishes shared NAT.
|
|
154
294
|
|
|
155
295
|
</Accordion>
|
|
156
296
|
</Accordions>
|
|
@@ -158,18 +298,17 @@ The gate was declared without `overridable: true`. Add it and redeploy — live
|
|
|
158
298
|
## Learn more
|
|
159
299
|
|
|
160
300
|
- [Flow](/docs/elements/flow) — the trigger pipeline gates plug into
|
|
161
|
-
- [
|
|
162
|
-
- [
|
|
301
|
+
- [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
|
|
302
|
+
- [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
|
|
303
|
+
- [Plugins](/docs/plugins) — username, magic link, OTP, TOTP, passkeys
|
|
304
|
+
- [Console · Gates](/docs/console/gates) — decision audit and posture
|
|
305
|
+
- [Vault](/docs/elements/vault) — credentials your policies protect
|
|
163
306
|
|
|
164
307
|
## Next
|
|
165
308
|
|
|
166
309
|
<Cards>
|
|
167
310
|
<Card title="Vault" description="Continue to Vault." href="/docs/elements/vault" />
|
|
168
|
-
<Card
|
|
169
|
-
title="Introduction"
|
|
170
|
-
description="Eight elements overview."
|
|
171
|
-
href="/docs/get-started/introduction"
|
|
172
|
-
/>
|
|
311
|
+
<Card title="Plugins" description="Sign-in method plugins under /auth." href="/docs/plugins" />
|
|
173
312
|
<Card
|
|
174
313
|
title="Console"
|
|
175
314
|
description="Panels derived from the Manifest."
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Elements"
|
|
3
|
+
description: "The eight irreducible parts of an OKE backend — Flow through AI, one page each."
|
|
4
|
+
icon: "Boxes"
|
|
5
|
+
source: "docs/spec/unified-theory.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Every backend concern collapses to one of these eight. Pick the element you need.
|
|
9
|
+
|
|
10
|
+
## Pages
|
|
11
|
+
|
|
12
|
+
<Cards>
|
|
13
|
+
<Card title="Flow" description="Behavior — on(Trigger) → Effects." href="/docs/elements/flow" />
|
|
14
|
+
<Card
|
|
15
|
+
title="Signal"
|
|
16
|
+
description="Data in motion — queues, pub/sub, streams."
|
|
17
|
+
href="/docs/elements/signal"
|
|
18
|
+
/>
|
|
19
|
+
<Card
|
|
20
|
+
title="Store"
|
|
21
|
+
description="Data at rest — sql, kv, files, index."
|
|
22
|
+
href="/docs/elements/store"
|
|
23
|
+
/>
|
|
24
|
+
<Card
|
|
25
|
+
title="Clock"
|
|
26
|
+
description="Time — schedules, intervals, durable sleeps."
|
|
27
|
+
href="/docs/elements/clock"
|
|
28
|
+
/>
|
|
29
|
+
<Card
|
|
30
|
+
title="Gate"
|
|
31
|
+
description="Permission to act — auth and rate limits."
|
|
32
|
+
href="/docs/elements/gate"
|
|
33
|
+
/>
|
|
34
|
+
<Card
|
|
35
|
+
title="Vault"
|
|
36
|
+
description="Protected knowledge — secrets and keys."
|
|
37
|
+
href="/docs/elements/vault"
|
|
38
|
+
/>
|
|
39
|
+
<Card
|
|
40
|
+
title="Channel"
|
|
41
|
+
description="Reaching humans — email, SMS, push."
|
|
42
|
+
href="/docs/elements/channel"
|
|
43
|
+
/>
|
|
44
|
+
<Card title="AI" description="Reaching machine intelligence." href="/docs/elements/ai" />
|
|
45
|
+
</Cards>
|
|
@@ -36,11 +36,11 @@ oke dev
|
|
|
36
36
|
The standard starter already exports a health Flow:
|
|
37
37
|
|
|
38
38
|
```typescript title="flows/main"
|
|
39
|
-
import { on, flow, http } from "okengine";
|
|
39
|
+
import { on, flow, http, gate } from "okengine";
|
|
40
40
|
import { z } from "zod";
|
|
41
41
|
|
|
42
42
|
export const health = on(
|
|
43
|
-
http.get("/health"),
|
|
43
|
+
http.get("/health").gate(gate.public),
|
|
44
44
|
flow({
|
|
45
45
|
out: z.object({ ok: z.literal(true) }),
|
|
46
46
|
do: () => ({ ok: true as const }),
|
|
@@ -75,7 +75,8 @@ const { data, error } = await api.main.health({});
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
`data` and `error` are inferred from the Flow contracts — no separate client
|
|
78
|
-
schema
|
|
78
|
+
schema. Bearer: `auth: { getToken, refresh }` (`okengine/client/auth` helpers;
|
|
79
|
+
`useSession` in `okengine/client-react`). Full options: [Client](/docs/reference/client).
|
|
79
80
|
|
|
80
81
|
</Step>
|
|
81
82
|
|