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/src/kernel/plugin.ts
CHANGED
|
@@ -14,8 +14,14 @@
|
|
|
14
14
|
* @see docs/spec/console.md §9.15
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import type { ChannelTemplateDecl } from "../elements/channel/declare.ts";
|
|
18
|
+
import type { ClockDecl } from "../elements/clock/declare.ts";
|
|
19
|
+
import type { GateDecl } from "../elements/gate/declare.ts";
|
|
20
|
+
import type { SignalDecl } from "../elements/signal/declare.ts";
|
|
21
|
+
import type { VaultSecretDecl } from "../elements/vault/declare.ts";
|
|
17
22
|
import type { SchemaInput } from "../validation/standard-schema.ts";
|
|
18
23
|
import type { AnyFlowDef, FlowErrorMap } from "./flow.ts";
|
|
24
|
+
import type { Binding } from "./on.ts";
|
|
19
25
|
import type { HookFn, HookStage } from "./hooks.ts";
|
|
20
26
|
|
|
21
27
|
/** Brand carrying accumulated decoration types through the builder. */
|
|
@@ -226,6 +232,13 @@ export interface PluginApi {
|
|
|
226
232
|
columns?: Readonly<Record<string, unknown>>,
|
|
227
233
|
options?: PluginTableOptions,
|
|
228
234
|
): PluginApi;
|
|
235
|
+
/**
|
|
236
|
+
* Contribute an HTTP (or other) Binding into the app router + adopted set.
|
|
237
|
+
* Required for auth method plugins — registry `flows[]` alone does not route.
|
|
238
|
+
*
|
|
239
|
+
* @param binding - Trigger + flow pair
|
|
240
|
+
*/
|
|
241
|
+
binding(binding: Binding): PluginApi;
|
|
229
242
|
/**
|
|
230
243
|
* Contribute a flow.
|
|
231
244
|
*
|
|
@@ -245,6 +258,36 @@ export interface PluginApi {
|
|
|
245
258
|
* @param schema - Standard Schema
|
|
246
259
|
*/
|
|
247
260
|
config(schema: SchemaInput): PluginApi;
|
|
261
|
+
/**
|
|
262
|
+
* Contribute a vault secret/config contract (merged into boot secrets).
|
|
263
|
+
*
|
|
264
|
+
* @param secret - Vault contract from `vault.secret(...)` / `vault.config(...)`
|
|
265
|
+
*/
|
|
266
|
+
vault(secret: VaultSecretDecl): PluginApi;
|
|
267
|
+
/**
|
|
268
|
+
* Contribute a named clock schedule (merged into boot clocks).
|
|
269
|
+
*
|
|
270
|
+
* @param decl - Clock from `clock(...)`
|
|
271
|
+
*/
|
|
272
|
+
clock(decl: ClockDecl): PluginApi;
|
|
273
|
+
/**
|
|
274
|
+
* Contribute a signal declaration (merged into boot signals).
|
|
275
|
+
*
|
|
276
|
+
* @param decl - Signal from `signal(...)`
|
|
277
|
+
*/
|
|
278
|
+
signal(decl: SignalDecl): PluginApi;
|
|
279
|
+
/**
|
|
280
|
+
* Contribute a gate declaration (merged into boot gates).
|
|
281
|
+
*
|
|
282
|
+
* @param decl - Gate from `gate.policy` / `gate.scope` / `gate.rate` / `gate.public`
|
|
283
|
+
*/
|
|
284
|
+
gate(decl: GateDecl): PluginApi;
|
|
285
|
+
/**
|
|
286
|
+
* Contribute a channel template (merged into boot channel templates).
|
|
287
|
+
*
|
|
288
|
+
* @param decl - Template from `channel.email.template(...)` etc.
|
|
289
|
+
*/
|
|
290
|
+
channelTemplate(decl: ChannelTemplateDecl): PluginApi;
|
|
248
291
|
}
|
|
249
292
|
|
|
250
293
|
/** Captured capability lists for the Manifest. */
|
|
@@ -276,9 +319,15 @@ export interface PluginRegistration {
|
|
|
276
319
|
readonly cli: readonly CliContribution[];
|
|
277
320
|
readonly images: readonly ImageRecipeContribution[];
|
|
278
321
|
readonly flows: readonly AnyFlowDef[];
|
|
322
|
+
readonly bindings: readonly Binding[];
|
|
279
323
|
readonly errors: FlowErrorMap;
|
|
280
324
|
readonly client: readonly ClientExtensionContribution[];
|
|
281
325
|
readonly configSchema: SchemaInput | undefined;
|
|
326
|
+
readonly vaultSecrets: readonly VaultSecretDecl[];
|
|
327
|
+
readonly clocks: readonly ClockDecl[];
|
|
328
|
+
readonly signals: readonly SignalDecl[];
|
|
329
|
+
readonly gates: readonly GateDecl[];
|
|
330
|
+
readonly channelTemplates: readonly ChannelTemplateDecl[];
|
|
282
331
|
}
|
|
283
332
|
|
|
284
333
|
/**
|
|
@@ -383,6 +432,12 @@ export interface PluginDef<D extends Record<string, unknown> = {}> {
|
|
|
383
432
|
columns?: Readonly<Record<string, unknown>>,
|
|
384
433
|
options?: PluginTableOptions,
|
|
385
434
|
): PluginDef<D>;
|
|
435
|
+
/**
|
|
436
|
+
* Queue a Binding contribution (joins router + adopted on app.plug).
|
|
437
|
+
*
|
|
438
|
+
* @param binding - Trigger + flow
|
|
439
|
+
*/
|
|
440
|
+
binding(binding: Binding): PluginDef<D>;
|
|
386
441
|
/**
|
|
387
442
|
* Queue a flow contribution.
|
|
388
443
|
*
|
|
@@ -402,6 +457,16 @@ export interface PluginDef<D extends Record<string, unknown> = {}> {
|
|
|
402
457
|
* @param schema - Standard Schema
|
|
403
458
|
*/
|
|
404
459
|
config(schema: SchemaInput): PluginDef<D>;
|
|
460
|
+
/** Queue a vault secret/config contract. */
|
|
461
|
+
vault(secret: VaultSecretDecl): PluginDef<D>;
|
|
462
|
+
/** Queue a clock schedule. */
|
|
463
|
+
clock(decl: ClockDecl): PluginDef<D>;
|
|
464
|
+
/** Queue a signal declaration. */
|
|
465
|
+
signal(decl: SignalDecl): PluginDef<D>;
|
|
466
|
+
/** Queue a gate declaration. */
|
|
467
|
+
gate(decl: GateDecl): PluginDef<D>;
|
|
468
|
+
/** Queue a channel template. */
|
|
469
|
+
channelTemplate(decl: ChannelTemplateDecl): PluginDef<D>;
|
|
405
470
|
}
|
|
406
471
|
|
|
407
472
|
/** Extract accumulated decoration types from a {@link PluginDef}. */
|
|
@@ -505,6 +570,12 @@ export function plugin(name: string, options: PluginOptions): PluginDef {
|
|
|
505
570
|
});
|
|
506
571
|
return def;
|
|
507
572
|
},
|
|
573
|
+
binding(binding) {
|
|
574
|
+
steps.push((api) => {
|
|
575
|
+
api.binding(binding);
|
|
576
|
+
});
|
|
577
|
+
return def;
|
|
578
|
+
},
|
|
508
579
|
flow(flowDef) {
|
|
509
580
|
steps.push((api) => {
|
|
510
581
|
api.flow(flowDef);
|
|
@@ -523,6 +594,36 @@ export function plugin(name: string, options: PluginOptions): PluginDef {
|
|
|
523
594
|
});
|
|
524
595
|
return def;
|
|
525
596
|
},
|
|
597
|
+
vault(secret) {
|
|
598
|
+
steps.push((api) => {
|
|
599
|
+
api.vault(secret);
|
|
600
|
+
});
|
|
601
|
+
return def;
|
|
602
|
+
},
|
|
603
|
+
clock(decl) {
|
|
604
|
+
steps.push((api) => {
|
|
605
|
+
api.clock(decl);
|
|
606
|
+
});
|
|
607
|
+
return def;
|
|
608
|
+
},
|
|
609
|
+
signal(decl) {
|
|
610
|
+
steps.push((api) => {
|
|
611
|
+
api.signal(decl);
|
|
612
|
+
});
|
|
613
|
+
return def;
|
|
614
|
+
},
|
|
615
|
+
gate(decl) {
|
|
616
|
+
steps.push((api) => {
|
|
617
|
+
api.gate(decl);
|
|
618
|
+
});
|
|
619
|
+
return def;
|
|
620
|
+
},
|
|
621
|
+
channelTemplate(decl) {
|
|
622
|
+
steps.push((api) => {
|
|
623
|
+
api.channelTemplate(decl);
|
|
624
|
+
});
|
|
625
|
+
return def;
|
|
626
|
+
},
|
|
526
627
|
};
|
|
527
628
|
|
|
528
629
|
return def;
|
|
@@ -22,11 +22,11 @@ afterEach(() => {
|
|
|
22
22
|
describe("registry isolation", () => {
|
|
23
23
|
test("consume (default): App B does not serve App A routes", async () => {
|
|
24
24
|
on(http.get("/a"), flow({ name: "app-a.ping", do: () => "a" }));
|
|
25
|
-
const appA = oke({ name: "app-a" });
|
|
25
|
+
const appA = oke({ autoBoot: false, name: "app-a" });
|
|
26
26
|
|
|
27
27
|
// No resetBindings() between the apps — the pre-fix leak scenario.
|
|
28
28
|
on(http.get("/b"), flow({ name: "app-b.ping", do: () => "b" }));
|
|
29
|
-
const appB = oke({ name: "app-b" });
|
|
29
|
+
const appB = oke({ autoBoot: false, name: "app-b" });
|
|
30
30
|
|
|
31
31
|
// Pre-fix this was 200: App B adopted App A's leftover binding.
|
|
32
32
|
expect((await appB.fetch(new Request("http://localhost/a"))).status).toBe(404);
|
|
@@ -46,10 +46,10 @@ describe("registry isolation", () => {
|
|
|
46
46
|
|
|
47
47
|
test("keep: today's behavior — registry survives construction", async () => {
|
|
48
48
|
on(http.get("/a"), flow({ name: "kept.ping", do: () => "a" }));
|
|
49
|
-
oke({ name: "app-a", registry: "keep" });
|
|
49
|
+
oke({ autoBoot: false, name: "app-a", registry: "keep" });
|
|
50
50
|
expect(listBindings()).toHaveLength(1);
|
|
51
51
|
|
|
52
|
-
const appB = oke({ name: "app-b", registry: "keep" });
|
|
52
|
+
const appB = oke({ autoBoot: false, name: "app-b", registry: "keep" });
|
|
53
53
|
expect((await appB.fetch(new Request("http://localhost/a"))).status).toBe(200);
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -60,7 +60,7 @@ describe("registry isolation", () => {
|
|
|
60
60
|
trigger: http.get("/b"),
|
|
61
61
|
flow: flow({ name: "only-b.ping", do: () => "b" }),
|
|
62
62
|
};
|
|
63
|
-
const app = oke({ name: "app-b", registry: "ignore", bindings: [onlyB] });
|
|
63
|
+
const app = oke({ autoBoot: false, name: "app-b", registry: "ignore", bindings: [onlyB] });
|
|
64
64
|
|
|
65
65
|
expect((await app.fetch(new Request("http://localhost/stray"))).status).toBe(404);
|
|
66
66
|
|
package/src/kernel/registry.ts
CHANGED
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
* @see docs/spec/console.md §9.15
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type {
|
|
15
|
+
import type { ChannelTemplateDecl } from "../elements/channel/declare.ts";
|
|
16
|
+
import type { ClockDecl } from "../elements/clock/declare.ts";
|
|
17
|
+
import type { GateDecl } from "../elements/gate/declare.ts";
|
|
18
|
+
import type { SignalDecl } from "../elements/signal/declare.ts";
|
|
19
|
+
import type { VaultSecretDecl } from "../elements/vault/declare.ts";
|
|
20
|
+
import type { SchemaInput } from "../validation/standard-schema.ts";
|
|
21
|
+
import type { AnyFlowDef, FlowErrorMap } from "./flow.ts";
|
|
16
22
|
import type { HookFn, HookMap, HookStage } from "./hooks.ts";
|
|
17
23
|
import { tagHookWithPlugin } from "./hook-timing.ts";
|
|
24
|
+
import type { Binding } from "./on.ts";
|
|
18
25
|
import type {
|
|
19
26
|
CliContribution,
|
|
20
27
|
ClientExtensionContribution,
|
|
@@ -30,8 +37,6 @@ import type {
|
|
|
30
37
|
PluginTableOptions,
|
|
31
38
|
TableContribution,
|
|
32
39
|
} from "./plugin.ts";
|
|
33
|
-
import type { SchemaInput } from "../validation/standard-schema.ts";
|
|
34
|
-
import type { AnyFlowDef } from "./flow.ts";
|
|
35
40
|
|
|
36
41
|
/** Attachment-point scope — the position is the scope. No escape hatch. */
|
|
37
42
|
export type PluginScope =
|
|
@@ -72,8 +77,14 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
|
|
|
72
77
|
const cli: CliContribution[] = [];
|
|
73
78
|
const images: ImageRecipeContribution[] = [];
|
|
74
79
|
const flows: AnyFlowDef[] = [];
|
|
80
|
+
const bindings: Binding[] = [];
|
|
75
81
|
const errors: FlowErrorMap = {};
|
|
76
82
|
const client: ClientExtensionContribution[] = [];
|
|
83
|
+
const vaultSecrets: VaultSecretDecl[] = [];
|
|
84
|
+
const clocks: ClockDecl[] = [];
|
|
85
|
+
const signals: SignalDecl[] = [];
|
|
86
|
+
const gates: GateDecl[] = [];
|
|
87
|
+
const channelTemplates: ChannelTemplateDecl[] = [];
|
|
77
88
|
const needs: string[] = [];
|
|
78
89
|
const declares: string[] = [];
|
|
79
90
|
const intercepts: string[] = [];
|
|
@@ -146,6 +157,13 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
|
|
|
146
157
|
pushDeclare(`table:${name}`);
|
|
147
158
|
return api;
|
|
148
159
|
},
|
|
160
|
+
binding(binding) {
|
|
161
|
+
bindings.push(binding);
|
|
162
|
+
flows.push(binding.flow);
|
|
163
|
+
pushDeclare(`binding:${binding.flow.name}`);
|
|
164
|
+
pushDeclare(`flow:${binding.flow.name}`);
|
|
165
|
+
return api;
|
|
166
|
+
},
|
|
149
167
|
flow(flowDef) {
|
|
150
168
|
flows.push(flowDef);
|
|
151
169
|
pushDeclare(`flow:${flowDef.name}`);
|
|
@@ -161,6 +179,31 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
|
|
|
161
179
|
pushDeclare("config");
|
|
162
180
|
return api;
|
|
163
181
|
},
|
|
182
|
+
vault(secret) {
|
|
183
|
+
vaultSecrets.push(secret);
|
|
184
|
+
pushDeclare(`vault:${secret.name}`);
|
|
185
|
+
return api;
|
|
186
|
+
},
|
|
187
|
+
clock(decl) {
|
|
188
|
+
clocks.push(decl);
|
|
189
|
+
pushDeclare(`clock:${decl.name}`);
|
|
190
|
+
return api;
|
|
191
|
+
},
|
|
192
|
+
signal(decl) {
|
|
193
|
+
signals.push(decl);
|
|
194
|
+
pushDeclare(`signal:${decl.name}`);
|
|
195
|
+
return api;
|
|
196
|
+
},
|
|
197
|
+
gate(decl) {
|
|
198
|
+
gates.push(decl);
|
|
199
|
+
pushDeclare(`gate:${decl.name}`);
|
|
200
|
+
return api;
|
|
201
|
+
},
|
|
202
|
+
channelTemplate(decl) {
|
|
203
|
+
channelTemplates.push(decl);
|
|
204
|
+
pushDeclare(`channel:${decl.name}`);
|
|
205
|
+
return api;
|
|
206
|
+
},
|
|
164
207
|
};
|
|
165
208
|
|
|
166
209
|
return {
|
|
@@ -186,9 +229,15 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
|
|
|
186
229
|
cli: cli.slice(),
|
|
187
230
|
images: images.slice(),
|
|
188
231
|
flows: flows.slice(),
|
|
232
|
+
bindings: bindings.slice(),
|
|
189
233
|
errors: { ...errors },
|
|
190
234
|
client: client.slice(),
|
|
191
235
|
configSchema,
|
|
236
|
+
vaultSecrets: vaultSecrets.slice(),
|
|
237
|
+
clocks: clocks.slice(),
|
|
238
|
+
signals: signals.slice(),
|
|
239
|
+
gates: gates.slice(),
|
|
240
|
+
channelTemplates: channelTemplates.slice(),
|
|
192
241
|
};
|
|
193
242
|
},
|
|
194
243
|
};
|
|
@@ -259,6 +308,18 @@ export interface PluginRegistry {
|
|
|
259
308
|
* plugin name). Used by `oke db` emit to merge plugin `field.*` tables.
|
|
260
309
|
*/
|
|
261
310
|
tableContributions(): readonly TableContribution[];
|
|
311
|
+
/** Vault contracts from installed plugins (first install per plugin name). */
|
|
312
|
+
vaultContributions(): readonly VaultSecretDecl[];
|
|
313
|
+
/** Clock schedules from installed plugins. */
|
|
314
|
+
clockContributions(): readonly ClockDecl[];
|
|
315
|
+
/** Signal declarations from installed plugins. */
|
|
316
|
+
signalContributions(): readonly SignalDecl[];
|
|
317
|
+
/** Gate declarations from installed plugins. */
|
|
318
|
+
gateContributions(): readonly GateDecl[];
|
|
319
|
+
/** Channel templates from installed plugins. */
|
|
320
|
+
channelTemplateContributions(): readonly ChannelTemplateDecl[];
|
|
321
|
+
/** HTTP Bindings from installed plugins (auth method plugins). */
|
|
322
|
+
bindingContributions(): readonly Binding[];
|
|
262
323
|
}
|
|
263
324
|
|
|
264
325
|
/**
|
|
@@ -413,11 +474,49 @@ export function createPluginRegistry(): PluginRegistry {
|
|
|
413
474
|
}
|
|
414
475
|
return out;
|
|
415
476
|
},
|
|
477
|
+
vaultContributions() {
|
|
478
|
+
return firstPerPlugin(installed, (r) => r.vaultSecrets);
|
|
479
|
+
},
|
|
480
|
+
clockContributions() {
|
|
481
|
+
return firstPerPlugin(installed, (r) => r.clocks);
|
|
482
|
+
},
|
|
483
|
+
signalContributions() {
|
|
484
|
+
return firstPerPlugin(installed, (r) => r.signals);
|
|
485
|
+
},
|
|
486
|
+
gateContributions() {
|
|
487
|
+
return firstPerPlugin(installed, (r) => r.gates);
|
|
488
|
+
},
|
|
489
|
+
channelTemplateContributions() {
|
|
490
|
+
return firstPerPlugin(installed, (r) => r.channelTemplates);
|
|
491
|
+
},
|
|
492
|
+
bindingContributions() {
|
|
493
|
+
return firstPerPlugin(installed, (r) => r.bindings);
|
|
494
|
+
},
|
|
416
495
|
};
|
|
417
496
|
|
|
418
497
|
return registry;
|
|
419
498
|
}
|
|
420
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Collect contributions from the first install of each plugin name.
|
|
502
|
+
*
|
|
503
|
+
* @param installed - Registry installs
|
|
504
|
+
* @param pick - Selector
|
|
505
|
+
*/
|
|
506
|
+
function firstPerPlugin<T>(
|
|
507
|
+
installed: readonly InstalledPlugin[],
|
|
508
|
+
pick: (r: PluginRegistration) => readonly T[],
|
|
509
|
+
): T[] {
|
|
510
|
+
const seenPlugins = new Set<string>();
|
|
511
|
+
const out: T[] = [];
|
|
512
|
+
for (const entry of installed) {
|
|
513
|
+
if (seenPlugins.has(entry.plugin.name)) continue;
|
|
514
|
+
seenPlugins.add(entry.plugin.name);
|
|
515
|
+
out.push(...pick(entry.registration));
|
|
516
|
+
}
|
|
517
|
+
return out;
|
|
518
|
+
}
|
|
519
|
+
|
|
421
520
|
/**
|
|
422
521
|
* Normalize plugin `.table()` args — supports columns + options, and legacy
|
|
423
522
|
* `{ plane }` as the second argument.
|
package/src/manifest/types.ts
CHANGED
|
@@ -327,6 +327,8 @@ export interface Plugin {
|
|
|
327
327
|
version?: string;
|
|
328
328
|
declares?: string[];
|
|
329
329
|
intercepts?: string[];
|
|
330
|
+
/** Declared `.needs()` dependencies (plugin names or element/driver ids). */
|
|
331
|
+
needs?: string[];
|
|
330
332
|
/** Optional metadata for `table:*` contributions. */
|
|
331
333
|
tables?: Record<string, PluginTable>;
|
|
332
334
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous sign-in Gate auth method plugin.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { issueSessionWithScopes } from "../auth/sessions.ts";
|
|
6
|
+
import { plugin, type PluginDef } from "../kernel/plugin.ts";
|
|
7
|
+
import {
|
|
8
|
+
AuthFailed,
|
|
9
|
+
AuthRateLimited,
|
|
10
|
+
SessionTokensOut,
|
|
11
|
+
bindPublicAuth,
|
|
12
|
+
createMethodRuntime,
|
|
13
|
+
flow,
|
|
14
|
+
type AuthMethodOptions,
|
|
15
|
+
} from "./auth/shared.ts";
|
|
16
|
+
|
|
17
|
+
/** Options for {@link anonymous}. */
|
|
18
|
+
export interface AnonymousPluginOptions extends AuthMethodOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Reserved for future disposable emails (`user@domain`). Unused in v1.
|
|
21
|
+
*/
|
|
22
|
+
readonly emailDomain?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Issue a user-plane session with a random principal id (no password).
|
|
27
|
+
*
|
|
28
|
+
* @param opts - Secret / session overrides
|
|
29
|
+
*/
|
|
30
|
+
export function anonymous(opts: AnonymousPluginOptions = {}): PluginDef {
|
|
31
|
+
const runtime = createMethodRuntime(opts);
|
|
32
|
+
|
|
33
|
+
const signIn = flow({
|
|
34
|
+
name: "auth.signInAnonymous",
|
|
35
|
+
unit: "auth",
|
|
36
|
+
plane: "user",
|
|
37
|
+
out: SessionTokensOut,
|
|
38
|
+
errors: { AuthFailed, AuthRateLimited },
|
|
39
|
+
do: async () => {
|
|
40
|
+
const userId = crypto.randomUUID();
|
|
41
|
+
const issued = await issueSessionWithScopes(runtime.sessions, runtime.crypto, {
|
|
42
|
+
id: userId,
|
|
43
|
+
plane: "user",
|
|
44
|
+
scopes: [],
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
accessToken: issued.accessToken,
|
|
48
|
+
refreshToken: issued.refreshToken,
|
|
49
|
+
accessExpiresAt: issued.accessExpiresAt,
|
|
50
|
+
userId,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return plugin("anonymous", { version: "0.0.1", config: { method: "anonymous" } })
|
|
56
|
+
.needs("auth")
|
|
57
|
+
.binding(bindPublicAuth("/sign-in/anonymous", signIn, "signIn"));
|
|
58
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for Gate auth method plugins.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { AUTH_RATE_PRESETS, AUTH_SESSION_GATE, bindAuthHttp } from "../../auth/bindings.ts";
|
|
7
|
+
import { getActiveGateAuthContext } from "../../auth/method-context.ts";
|
|
8
|
+
import { createSessionStore, type SessionCrypto, type SessionStore } from "../../auth/sessions.ts";
|
|
9
|
+
import { gate } from "../../elements/gate.ts";
|
|
10
|
+
import type { GateDecl } from "../../elements/gate/declare.ts";
|
|
11
|
+
import { fail } from "../../kernel/errors.ts";
|
|
12
|
+
import { flow, type AnyFlowDef } from "../../kernel/flow.ts";
|
|
13
|
+
import type { Binding } from "../../kernel/on.ts";
|
|
14
|
+
import { http } from "../../kernel/triggers.ts";
|
|
15
|
+
import { createEnv } from "../../runtime/primitives.ts";
|
|
16
|
+
|
|
17
|
+
export const AuthFailed = z.object({ reason: z.string().optional() });
|
|
18
|
+
export const AuthRateLimited = z.object({ reason: z.string() });
|
|
19
|
+
|
|
20
|
+
export const SessionTokensOut = z.object({
|
|
21
|
+
accessToken: z.string(),
|
|
22
|
+
refreshToken: z.string(),
|
|
23
|
+
accessExpiresAt: z.number(),
|
|
24
|
+
userId: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** Common options for auth method plugins (session issue). */
|
|
28
|
+
export interface AuthMethodOptions {
|
|
29
|
+
/** HMAC secret; falls back to `OKE_AUTH_SECRET`, then a dev default. */
|
|
30
|
+
readonly secret?: string;
|
|
31
|
+
/** Shared session store (prefer the same store as `gate.auth`). */
|
|
32
|
+
readonly sessions?: SessionStore;
|
|
33
|
+
/** Injectable clock. */
|
|
34
|
+
readonly now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve HMAC secret for method plugins.
|
|
39
|
+
*
|
|
40
|
+
* @param opts - Optional explicit secret
|
|
41
|
+
*/
|
|
42
|
+
export function resolveMethodSecret(opts: { readonly secret?: string } = {}): string {
|
|
43
|
+
if (opts.secret && opts.secret.length > 0) return opts.secret;
|
|
44
|
+
const active = getActiveGateAuthContext();
|
|
45
|
+
if (active?.secret) return active.secret;
|
|
46
|
+
const fromEnv = createEnv().get("OKE_AUTH_SECRET");
|
|
47
|
+
if (fromEnv && fromEnv.length > 0) return fromEnv;
|
|
48
|
+
return "oke-dev-auth-secret-change-me";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Session store + crypto closed over by a method plugin factory.
|
|
53
|
+
* Prefers explicit opts, then the active `gate.auth` context from `oke()`.
|
|
54
|
+
*
|
|
55
|
+
* @param opts - Common method options
|
|
56
|
+
*/
|
|
57
|
+
export function createMethodRuntime(opts: AuthMethodOptions = {}): {
|
|
58
|
+
readonly sessions: SessionStore;
|
|
59
|
+
readonly crypto: SessionCrypto;
|
|
60
|
+
now(): number;
|
|
61
|
+
} {
|
|
62
|
+
const active = getActiveGateAuthContext();
|
|
63
|
+
const sessions = opts.sessions ?? active?.sessions ?? createSessionStore();
|
|
64
|
+
const now = opts.now ?? active?.now ?? (() => Date.now());
|
|
65
|
+
return {
|
|
66
|
+
sessions,
|
|
67
|
+
now,
|
|
68
|
+
crypto: {
|
|
69
|
+
secret: resolveMethodSecret(opts),
|
|
70
|
+
now,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Public + rate gate chain for credential-ish auth paths.
|
|
77
|
+
*
|
|
78
|
+
* @param kind - Preset key
|
|
79
|
+
*/
|
|
80
|
+
export function authPublicGates(kind: keyof typeof AUTH_RATE_PRESETS = "otp"): GateDecl[] {
|
|
81
|
+
const preset = AUTH_RATE_PRESETS[kind];
|
|
82
|
+
return [
|
|
83
|
+
gate.public,
|
|
84
|
+
gate.rate({
|
|
85
|
+
max: preset.max,
|
|
86
|
+
per: preset.per,
|
|
87
|
+
keyBy: preset.keyBy,
|
|
88
|
+
description: `Auth ${kind} rate limit`,
|
|
89
|
+
}),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Bind a public auth Flow under `/auth/...`.
|
|
95
|
+
*
|
|
96
|
+
* @param path - Path under `/auth` (e.g. `/sign-in/username`)
|
|
97
|
+
* @param flowDef - Flow
|
|
98
|
+
* @param kind - Rate preset
|
|
99
|
+
*/
|
|
100
|
+
export function bindPublicAuth(
|
|
101
|
+
path: string,
|
|
102
|
+
flowDef: AnyFlowDef,
|
|
103
|
+
kind: keyof typeof AUTH_RATE_PRESETS = "otp",
|
|
104
|
+
): Binding {
|
|
105
|
+
const gates = authPublicGates(kind);
|
|
106
|
+
let trigger = http.post(`/auth${path}`);
|
|
107
|
+
for (const g of gates) trigger = trigger.gate(g);
|
|
108
|
+
return bindAuthHttp(trigger, flowDef);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Bind a session-gated auth Flow under `/auth/...`.
|
|
113
|
+
*
|
|
114
|
+
* @param path - Path under `/auth`
|
|
115
|
+
* @param flowDef - Flow
|
|
116
|
+
*/
|
|
117
|
+
export function bindSessionAuth(path: string, flowDef: AnyFlowDef): Binding {
|
|
118
|
+
return bindAuthHttp(http.post(`/auth${path}`).gate(AUTH_SESSION_GATE), flowDef);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { AUTH_SESSION_GATE, fail, flow, http, z };
|