okengine 0.18.5 → 0.19.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/AGENTS.md +18 -10
- package/README.md +2 -2
- package/package.json +51 -50
- package/site/content/docs/ai/llms-txt.mdx +1 -1
- package/site/content/docs/ai/meta.json +1 -1
- package/site/content/docs/ai/skills.mdx +4 -1
- package/site/content/docs/ai/try-it.mdx +58 -0
- package/site/content/docs/client/auth.mdx +208 -0
- package/site/content/docs/client/calling.mdx +451 -0
- package/site/content/docs/client/index.mdx +209 -0
- package/site/content/docs/client/live.mdx +234 -0
- package/site/content/docs/client/meta.json +5 -0
- package/site/content/docs/client/react.mdx +249 -0
- package/site/content/docs/elements/ai/agents.mdx +232 -0
- package/site/content/docs/elements/ai/index.mdx +341 -0
- package/site/content/docs/elements/ai/mcp.mdx +275 -0
- package/site/content/docs/elements/ai/meta.json +5 -0
- package/site/content/docs/elements/ai/models.mdx +283 -0
- package/site/content/docs/elements/ai/prompts.mdx +256 -0
- package/site/content/docs/elements/channel/email.mdx +266 -0
- package/site/content/docs/elements/channel/index.mdx +344 -0
- package/site/content/docs/elements/channel/meta.json +5 -0
- package/site/content/docs/elements/channel/push.mdx +218 -0
- package/site/content/docs/elements/channel/receipts.mdx +206 -0
- package/site/content/docs/elements/channel/sms.mdx +264 -0
- package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
- package/site/content/docs/elements/clock/index.mdx +300 -0
- package/site/content/docs/elements/clock/meta.json +5 -0
- package/site/content/docs/elements/clock/schedules.mdx +508 -0
- package/site/content/docs/elements/clock/sleep.mdx +445 -0
- package/site/content/docs/elements/flow/consumers.mdx +707 -0
- package/site/content/docs/elements/flow/http.mdx +1080 -0
- package/site/content/docs/elements/flow/index.mdx +618 -0
- package/site/content/docs/elements/flow/meta.json +5 -0
- package/site/content/docs/elements/flow/routing.mdx +559 -0
- package/site/content/docs/elements/flow/workflows.mdx +640 -0
- package/site/content/docs/elements/gate/auth.mdx +371 -0
- package/site/content/docs/elements/gate/authorization.mdx +298 -0
- package/site/content/docs/elements/gate/index.mdx +418 -0
- package/site/content/docs/elements/gate/meta.json +5 -0
- package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
- package/site/content/docs/elements/gate/rls.mdx +303 -0
- package/site/content/docs/elements/gate/tenancy.mdx +372 -0
- package/site/content/docs/elements/index.mdx +20 -10
- package/site/content/docs/elements/signal/broadcast.mdx +517 -0
- package/site/content/docs/elements/signal/index.mdx +352 -0
- package/site/content/docs/elements/signal/live.mdx +590 -0
- package/site/content/docs/elements/signal/meta.json +5 -0
- package/site/content/docs/elements/signal/once.mdx +596 -0
- package/site/content/docs/elements/store/files.mdx +659 -0
- package/site/content/docs/elements/store/index.mdx +310 -0
- package/site/content/docs/elements/store/kv.mdx +629 -0
- package/site/content/docs/elements/store/meta.json +5 -0
- package/site/content/docs/elements/store/search.mdx +946 -0
- package/site/content/docs/elements/store/sql.mdx +937 -0
- package/site/content/docs/elements/vault/config.mdx +256 -0
- package/site/content/docs/elements/vault/index.mdx +356 -0
- package/site/content/docs/elements/vault/meta.json +5 -0
- package/site/content/docs/elements/vault/rotation.mdx +273 -0
- package/site/content/docs/elements/vault/secrets.mdx +285 -0
- package/site/content/docs/index.mdx +63 -36
- package/site/content/docs/meta.json +8 -7
- package/site/content/docs/plugins/cors.mdx +2 -1
- package/site/content/docs/plugins/csrf.mdx +4 -1
- package/site/content/docs/plugins/headers.mdx +2 -1
- package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
- package/site/content/docs/plugins/magic-link.mdx +6 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
- package/site/content/docs/plugins/oauth.mdx +1 -1
- package/site/content/docs/plugins/otp.mdx +12 -1
- package/site/content/docs/plugins/passkey.mdx +39 -15
- package/site/content/docs/plugins/two-factor.mdx +91 -34
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +3 -3
- package/site/content/docs/recipes/index.mdx +18 -28
- package/site/content/docs/recipes/mailpit.mdx +1 -1
- package/site/content/docs/recipes/meilisearch.mdx +3 -3
- package/site/content/docs/recipes/meta.json +1 -4
- package/site/content/docs/recipes/nginx.mdx +2 -2
- package/site/content/docs/recipes/openrouter.mdx +283 -0
- package/site/content/docs/recipes/pgdog.mdx +3 -3
- package/site/content/docs/recipes/postgres.mdx +1 -2
- package/site/content/docs/recipes/rustfs.mdx +2 -2
- package/site/content/docs/recipes/traefik.mdx +3 -3
- package/site/content/docs/reference/cli.mdx +231 -0
- package/site/content/docs/reference/configuration.mdx +30 -10
- package/site/content/docs/reference/environment-variables.mdx +93 -20
- package/site/content/docs/reference/errors.mdx +132 -29
- package/site/content/docs/reference/fx.mdx +137 -73
- package/site/content/docs/reference/index.mdx +13 -21
- package/site/content/docs/reference/meta.json +4 -5
- package/site/content/docs/reference/okid.mdx +42 -17
- package/site/content/docs/reference/plugins.mdx +4 -3
- package/site/content/docs/reference/security.mdx +197 -0
- package/site/content/docs/understand/meta.json +5 -0
- package/site/content/docs/understand/the-anatomy.mdx +132 -0
- package/site/content/docs/understand/the-model.mdx +32 -0
- package/site/content/docs/understand/the-problem.mdx +74 -0
- package/site/content/docs/understand/the-vocabulary.mdx +26 -0
- package/src/auth/api-keys.ts +2 -1
- package/src/auth/bindings.ts +64 -16
- package/src/auth/gate-auth.test.ts +6 -1
- package/src/auth/identity.ts +152 -5
- package/src/auth/index.ts +29 -0
- package/src/auth/invites.ts +2 -1
- package/src/auth/method-context.ts +41 -0
- package/src/auth/oauth-as/crypto.ts +2 -1
- package/src/auth/oauth-as/stores.ts +2 -1
- package/src/auth/operator.ts +2 -1
- package/src/auth/sessions-jwt.test.ts +77 -0
- package/src/auth/sessions.ts +20 -4
- package/src/auth/tenants.ts +3 -2
- package/src/auth/two-factor-challenge.test.ts +61 -0
- package/src/auth/two-factor-challenge.ts +284 -0
- package/src/auth/verification.ts +5 -0
- package/src/bench/REPORT.md +49 -0
- package/src/bench/g03-signal-once.bench.ts +1 -1
- package/src/bench/g10-observability-contention.bench.ts +1 -1
- package/src/bench/g17-hybrid-search.bench.ts +572 -0
- package/src/bench/load-app.ts +2 -10
- package/src/cli/ai-setup/ai-setup.test.ts +331 -42
- package/src/cli/ai-setup/apply.ts +294 -52
- package/src/cli/ai-setup/catalog.ts +238 -1342
- package/src/cli/ai-setup/index.ts +14 -99
- package/src/cli/ai-setup/prompts.ts +60 -443
- package/src/cli/ask-seed.test.ts +96 -0
- package/src/cli/ask-seed.ts +82 -0
- package/src/cli/ask-vault-gaps.test.ts +84 -0
- package/src/cli/ask-vault-gaps.ts +146 -0
- package/src/cli/build.ts +2 -2
- package/src/cli/client-add.test.ts +26 -1
- package/src/cli/client-add.ts +106 -27
- package/src/cli/db-seed.ts +2 -0
- package/src/cli/db.ts +150 -11
- package/src/cli/dev.ts +119 -211
- package/src/cli/docker-clean.ts +2 -2
- package/src/cli/docker-cli.test.ts +1 -1
- package/src/cli/doctor-diff.ts +4 -2
- package/src/cli/doctor-pii.test.ts +1 -1
- package/src/cli/doctor.ts +14 -1
- package/src/cli/hero-meta.test.ts +4 -2
- package/src/cli/load-config.images.test.ts +8 -10
- package/src/cli/load-config.ts +1 -1
- package/src/cli/registry.ts +22 -10
- package/src/cli/replay.ts +3 -1
- package/src/cli/start.ts +1 -1
- package/src/cli/tui/keys.ts +2 -3
- package/src/client/auth/cookies.ts +74 -0
- package/src/client/auth/create-auth-client.ts +636 -0
- package/src/client/auth/denials.ts +99 -0
- package/src/client/auth/session.ts +283 -0
- package/src/client/auth.test.ts +251 -0
- package/src/client/auth.ts +39 -114
- package/src/client/create-with-session.ts +245 -0
- package/src/client/create.ts +51 -5
- package/src/client/index.ts +11 -1
- package/src/client/live.ts +11 -79
- package/src/client/notes-contract.test.ts +5 -10
- package/src/client/sse.ts +137 -0
- package/src/client/stream.ts +147 -0
- package/src/client/transport.test.ts +32 -0
- package/src/client/transport.ts +123 -26
- package/src/client/types.ts +118 -14
- package/src/client-react/index.ts +185 -24
- package/src/client-react/use-live-query.ts +13 -3
- package/src/compiler/aot.test.ts +7 -4
- package/src/compiler/effects-embed.test.ts +56 -0
- package/src/compiler/effects-fetch.test.ts +43 -0
- package/src/compiler/effects-infer.ts +58 -2
- package/src/compiler/extract.test.ts +257 -43
- package/src/compiler/extract.ts +566 -48
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
- package/src/compiler/fixtures/skyport.expected.json +4 -4
- package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
- package/src/compiler/response.ts +2 -2
- package/src/compiler/search-writer-isolation.test.ts +40 -0
- package/src/config/index.ts +11 -0
- package/src/console/server/app.ts +17 -23
- package/src/console/server/bind.ts +4 -0
- package/src/console/server/console.test.ts +2 -2
- package/src/console/server/flows-invoke.test.ts +50 -34
- package/src/console/server/flows.ts +436 -255
- package/src/console/server/runs-ingest.test.ts +6 -3
- package/src/console/server/serve.ts +1 -1
- package/src/console/server/signals.test.ts +1 -5
- package/src/console/server/signals.ts +8 -6
- package/src/console/server/state.ts +6 -1
- package/src/console/server/store.ts +1 -1
- package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
- package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
- package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
- package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
- package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
- package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
- package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
- package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
- package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
- package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
- package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
- package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
- package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
- package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
- package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
- package/src/console/ui-next/dist/index.html +6 -13
- package/src/console/ui-next/seed-invoke-host.ts +21 -18
- package/src/console/ui-next/src/client.ts +7 -2
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
- package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
- package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
- package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
- package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
- package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
- package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
- package/src/docker/ai-model-status.test.ts +1 -12
- package/src/docker/ai-model-status.ts +10 -38
- package/src/docker/compose-up.test.ts +100 -0
- package/src/docker/compose-up.ts +292 -0
- package/src/docker/compose.ts +4 -14
- package/src/docker/derive.ts +9 -34
- package/src/docker/docker.test.ts +16 -261
- package/src/docker/images-config.test.ts +13 -25
- package/src/docker/index.ts +1 -24
- package/src/docker/recipes/index.ts +0 -23
- package/src/docker/recipes/pgdog.ts +6 -4
- package/src/docker/stack-id.test.ts +2 -2
- package/src/docker/stack-id.ts +0 -1
- package/src/docker/types.ts +2 -2
- package/src/drivers/ai-anthropic.ts +10 -0
- package/src/drivers/ai-openai-compatible.ts +34 -7
- package/src/drivers/ai-providers.test.ts +0 -107
- package/src/drivers/ai-stream.test.ts +0 -34
- package/src/drivers/ai-types.ts +17 -8
- package/src/drivers/channel-fcm.ts +6 -1
- package/src/drivers/channel-msegat.ts +6 -1
- package/src/drivers/channel-resend.ts +5 -1
- package/src/drivers/channel-smtp.ts +5 -1
- package/src/drivers/channel-sndr.ts +10 -1
- package/src/drivers/channel-taqnyat-mail.ts +5 -1
- package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
- package/src/drivers/channel-taqnyat.ts +6 -1
- package/src/drivers/channel-types.ts +10 -0
- package/src/drivers/channel-unifonic.ts +6 -1
- package/src/drivers/channel-wa-cloud.ts +6 -1
- package/src/drivers/channel-webpush.ts +6 -1
- package/src/drivers/external.ts +33 -0
- package/src/drivers/index.ts +0 -10
- package/src/drivers/meilisearch.ts +6 -0
- package/src/drivers/oauth-types.ts +4 -0
- package/src/drivers/postgres.test.ts +74 -3
- package/src/drivers/postgres.ts +175 -6
- package/src/drivers/signal-types.ts +5 -5
- package/src/drivers/types.ts +7 -0
- package/src/drivers/vault-types.ts +5 -0
- package/src/elements/ai/declare.ts +17 -4
- package/src/elements/ai/eval.ts +1 -1
- package/src/elements/ai/mcp-http.ts +1 -1
- package/src/elements/ai/pii.ts +1 -1
- package/src/elements/ai/providers.test.ts +289 -0
- package/src/elements/ai/providers.ts +169 -0
- package/src/elements/ai/runtime.ts +68 -5
- package/src/elements/ai/schema.ts +2 -2
- package/src/elements/ai.test.ts +7 -7
- package/src/elements/ai.ts +17 -1
- package/src/elements/channel/runtime.ts +105 -12
- package/src/elements/clock/cron-fields.test.ts +144 -0
- package/src/elements/clock/cron-fields.ts +185 -0
- package/src/elements/clock/declare.ts +247 -3
- package/src/elements/clock.ts +21 -1
- package/src/elements/index.ts +15 -0
- package/src/elements/signal/chaos-child.ts +1 -2
- package/src/elements/signal/declare.ts +65 -37
- package/src/elements/signal/delivery-modes.test.ts +11 -31
- package/src/elements/signal/dry-run-replay.test.ts +1 -7
- package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
- package/src/elements/signal/key-ordering.test.ts +6 -26
- package/src/elements/signal/lease-reclaim.test.ts +2 -6
- package/src/elements/signal/optional-emit.test.ts +6 -9
- package/src/elements/signal/order-lifecycle.test.ts +4 -17
- package/src/elements/signal/orphan-messages.test.ts +4 -15
- package/src/elements/signal/reconcile.test.ts +2 -2
- package/src/elements/signal/runtime.ts +1 -1
- package/src/elements/signal/schema-emit.test.ts +4 -8
- package/src/elements/signal.test.ts +24 -49
- package/src/elements/signal.ts +9 -2
- package/src/elements/store/domain-ddl.test.ts +1 -1
- package/src/elements/store/live-http.test.ts +9 -4
- package/src/elements/store/prepare-row.test.ts +123 -0
- package/src/elements/store/resource.ts +43 -12
- package/src/elements/store/schema-decl.ts +117 -0
- package/src/elements/store/search-backfill.ts +205 -0
- package/src/elements/store/search-bind.ts +66 -0
- package/src/elements/store/search-bm25.ts +78 -0
- package/src/elements/store/search-ddl.ts +156 -0
- package/src/elements/store/search-embed-flow.ts +141 -0
- package/src/elements/store/search-errors.ts +34 -0
- package/src/elements/store/search-fusion.ts +112 -0
- package/src/elements/store/search-lsh.ts +179 -0
- package/src/elements/store/search-runtime.ts +300 -0
- package/src/elements/store/search.test.ts +144 -0
- package/src/elements/store/sql-session.test.ts +1 -0
- package/src/elements/store/sql-session.ts +83 -3
- package/src/elements/store/table.ts +23 -2
- package/src/elements/store/upsert-app.test.ts +1 -3
- package/src/elements/store.ts +50 -0
- package/src/full.ts +4 -2
- package/src/http.ts +4 -1
- package/src/i18n/catalogs/ar.ts +14 -6
- package/src/i18n/catalogs/en.ts +14 -6
- package/src/index.ts +6 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
- package/src/kernel/adopt-routes.ts +9 -1
- package/src/kernel/app-auth.ts +5 -0
- package/src/kernel/app.ts +102 -9
- package/src/kernel/auto-cache.test.ts +6 -12
- package/src/kernel/auto-registry.test.ts +3 -3
- package/src/kernel/boot-bind/ai.test.ts +22 -19
- package/src/kernel/boot-bind/ai.ts +5 -43
- package/src/kernel/boot-bind/clock.ts +23 -5
- package/src/kernel/boot-bind/honor-config.test.ts +4 -4
- package/src/kernel/boot.test.ts +11 -6
- package/src/kernel/boot.ts +66 -16
- package/src/kernel/boundary-contract.ts +93 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/call.ts +89 -0
- package/src/kernel/capability.ts +6 -0
- package/src/kernel/client-descriptor.ts +123 -0
- package/src/kernel/clock-timezone.test.ts +80 -0
- package/src/kernel/correlation.test.ts +1 -1
- package/src/kernel/dry-run.ts +11 -8
- package/src/kernel/effects-stamping.test.ts +42 -9
- package/src/kernel/effects.test.ts +4 -2
- package/src/kernel/effects.ts +51 -9
- package/src/kernel/errors-channel.ts +17 -0
- package/src/kernel/errors-live-resume.ts +3 -2
- package/src/kernel/errors-tenant.ts +7 -4
- package/src/kernel/errors.registry-helpers.ts +96 -0
- package/src/kernel/errors.registry.test.ts +139 -25
- package/src/kernel/errors.ts +102 -23
- package/src/kernel/external-effects.test.ts +196 -0
- package/src/kernel/flow.test.ts +13 -4
- package/src/kernel/flow.ts +69 -71
- package/src/kernel/fx-ask-telemetry.test.ts +2 -2
- package/src/kernel/fx-dead-letters.test.ts +3 -3
- package/src/kernel/fx-fetch.ts +55 -0
- package/src/kernel/fx-live-stream.ts +2 -2
- package/src/kernel/fx-live.test.ts +8 -8
- package/src/kernel/fx.test.ts +17 -11
- package/src/kernel/fx.ts +180 -57
- package/src/kernel/horizontal-child.ts +1 -1
- package/src/kernel/http-query.test.ts +3 -6
- package/src/kernel/index.ts +13 -3
- package/src/kernel/instance-id.ts +1 -1
- package/src/kernel/instances.test.ts +40 -0
- package/src/kernel/instances.ts +47 -9
- package/src/kernel/live-http.test.ts +6 -7
- package/src/kernel/live-http.ts +6 -2
- package/src/kernel/live-resume.test.ts +1 -1
- package/src/kernel/mcp-tool.test.ts +3 -3
- package/src/kernel/on.ts +98 -4
- package/src/kernel/pipeline.test.ts +12 -15
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/stamp-http.test.ts +3 -3
- package/src/kernel/triggers.ts +210 -95
- package/src/kernel-entry.ts +0 -1
- package/src/manifest/diff.ts +11 -1
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/types.ts +46 -2
- package/src/mcp/docs-index.ts +3 -3
- package/src/mcp/docs-mcp.test.ts +2 -2
- package/src/mcp/docs-tools.ts +1 -1
- package/src/okid.test.ts +56 -0
- package/src/okid.ts +55 -11
- package/src/plugins/anonymous.ts +8 -4
- package/src/plugins/auth/shared.ts +27 -3
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
- package/src/plugins/auth-methods.security.test.ts +561 -23
- package/src/plugins/config-source.ts +1 -1
- package/src/plugins/csrf.test.ts +65 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +36 -16
- package/src/plugins/oauth/shared.ts +3 -1
- package/src/plugins/oauth.ts +22 -14
- package/src/plugins/otp.ts +37 -21
- package/src/plugins/passkey-webauthn.ts +9 -6
- package/src/plugins/passkey.ts +98 -26
- package/src/plugins/pre-account-hijack.test.ts +223 -0
- package/src/plugins/two-factor.ts +466 -37
- package/src/plugins/username.ts +19 -7
- package/src/release/build-lib.ts +1 -0
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +1 -1
- package/src/runs/collect.test.ts +0 -2
- package/src/runtime/json-code-block.test.ts +250 -20
- package/src/runtime/json-code-block.ts +1261 -41
- package/src/term.test.ts +1 -1
- package/src/test/create-test-app.test.ts +12 -12
- package/src/test/live-signals.test.ts +6 -6
- package/src/test/provisions.integration.test.ts +10 -14
- package/src/test/reset-element-registries.ts +1 -1
- package/src/test/tenant-isolation.test.ts +12 -6
- package/site/content/docs/deployment/docker-swarm.mdx +0 -164
- package/site/content/docs/deployment/docker.mdx +0 -227
- package/site/content/docs/deployment/index.mdx +0 -83
- package/site/content/docs/deployment/kubernetes.mdx +0 -176
- package/site/content/docs/deployment/meta.json +0 -5
- package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
- package/site/content/docs/elements/ai.mdx +0 -385
- package/site/content/docs/elements/channel.mdx +0 -346
- package/site/content/docs/elements/clock.mdx +0 -244
- package/site/content/docs/elements/flow.mdx +0 -420
- package/site/content/docs/elements/gate.mdx +0 -436
- package/site/content/docs/elements/signal.mdx +0 -380
- package/site/content/docs/elements/store.mdx +0 -1099
- package/site/content/docs/elements/vault.mdx +0 -405
- package/site/content/docs/get-started/basic-usage.mdx +0 -173
- package/site/content/docs/get-started/index.mdx +0 -43
- package/site/content/docs/get-started/installation.mdx +0 -220
- package/site/content/docs/get-started/introduction.mdx +0 -144
- package/site/content/docs/get-started/meta.json +0 -13
- package/site/content/docs/get-started/project-structure.mdx +0 -925
- package/site/content/docs/get-started/testing.mdx +0 -328
- package/site/content/docs/get-started/why.mdx +0 -114
- package/site/content/docs/recipes/llama-cpp.mdx +0 -151
- package/site/content/docs/recipes/ollama.mdx +0 -142
- package/site/content/docs/recipes/sglang.mdx +0 -105
- package/site/content/docs/recipes/vllm.mdx +0 -106
- package/site/content/docs/reference/cli.md +0 -232
- package/site/content/docs/reference/client.mdx +0 -469
- package/site/content/docs/reference/security.md +0 -74
- package/src/cli/ai-setup/detect-ollama.ts +0 -213
- package/src/cli/ai-setup/recommend.test.ts +0 -225
- package/src/cli/ai-setup/recommend.ts +0 -225
- package/src/cli/dev-controls.test.ts +0 -122
- package/src/cli/dev-controls.ts +0 -164
- package/src/cli/tui/DevLive.tsx +0 -126
- package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
- package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
- package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
- package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
- package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
- package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
- package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
- package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
- package/src/docker/ollama-pull.ts +0 -232
- package/src/docker/recipes/llama-cpp.ts +0 -298
- package/src/docker/recipes/ollama.ts +0 -44
- package/src/docker/recipes/sglang.ts +0 -55
- package/src/docker/recipes/vllm.ts +0 -44
- package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
- package/src/drivers/ai-ollama.integration.test.ts +0 -184
- package/src/drivers/ai-ollama.ts +0 -389
- package/src/drivers/ollama.ts +0 -14
|
@@ -59,7 +59,7 @@ export interface ConfigSourceOptions<T extends object> {
|
|
|
59
59
|
/**
|
|
60
60
|
* A live config box for one plugin. `current()` is synchronous and never
|
|
61
61
|
* throws — safe to read from any hook. `sync()` is the refresh flow the
|
|
62
|
-
* app binds once: `on(
|
|
62
|
+
* app binds once: `on(syncClock, source.sync())`.
|
|
63
63
|
*/
|
|
64
64
|
export interface ConfigSource<T extends object> {
|
|
65
65
|
readonly [configSourceBrand]: true;
|
package/src/plugins/csrf.test.ts
CHANGED
|
@@ -117,3 +117,68 @@ describe("csrf plugin", () => {
|
|
|
117
117
|
expect(res.status).toBe(200);
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("csrf soft-require when cookies enabled", () => {
|
|
122
|
+
test("prod boots refuse without csrf plugin", async () => {
|
|
123
|
+
const app = oke({
|
|
124
|
+
autoBoot: false,
|
|
125
|
+
name: `csrf-cookie-${crypto.randomUUID()}`,
|
|
126
|
+
env: "prod",
|
|
127
|
+
registry: "ignore",
|
|
128
|
+
gate: {
|
|
129
|
+
auth: {
|
|
130
|
+
secret: "test-secret-at-least-32-characters!!",
|
|
131
|
+
cookies: { enabled: true },
|
|
132
|
+
http: false,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
await expect(app.boot()).rejects.toThrow(/csrf plugin/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("test/dev boots warn without csrf plugin", async () => {
|
|
140
|
+
const warnings: string[] = [];
|
|
141
|
+
const prev = console.warn;
|
|
142
|
+
console.warn = (...args: unknown[]) => {
|
|
143
|
+
warnings.push(args.map(String).join(" "));
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
const app = oke({
|
|
147
|
+
autoBoot: false,
|
|
148
|
+
name: `csrf-cookie-dev-${crypto.randomUUID()}`,
|
|
149
|
+
env: "test",
|
|
150
|
+
registry: "ignore",
|
|
151
|
+
gate: {
|
|
152
|
+
auth: {
|
|
153
|
+
secret: "test-secret-at-least-32-characters!!",
|
|
154
|
+
cookies: { enabled: true },
|
|
155
|
+
http: false,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
await app.boot({ env: "test" });
|
|
160
|
+
expect(warnings.some((w) => w.includes("csrf"))).toBe(true);
|
|
161
|
+
await app.stop();
|
|
162
|
+
} finally {
|
|
163
|
+
console.warn = prev;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("boots with csrf plugin when cookies enabled", async () => {
|
|
168
|
+
const app = oke({
|
|
169
|
+
autoBoot: false,
|
|
170
|
+
name: `csrf-cookie-ok-${crypto.randomUUID()}`,
|
|
171
|
+
env: "test",
|
|
172
|
+
registry: "ignore",
|
|
173
|
+
gate: {
|
|
174
|
+
auth: {
|
|
175
|
+
secret: "test-secret-at-least-32-characters!!",
|
|
176
|
+
cookies: { enabled: true },
|
|
177
|
+
http: false,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}).plug(csrf({ allowNoHeader: false }));
|
|
181
|
+
await app.boot({ env: "test" });
|
|
182
|
+
await app.stop();
|
|
183
|
+
});
|
|
184
|
+
});
|
package/src/plugins/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export {
|
|
|
15
15
|
type ConfigSourceOptions,
|
|
16
16
|
} from "./config-source.ts";
|
|
17
17
|
export { cors, type CorsOptions } from "./cors.ts";
|
|
18
|
+
export { csrf, type CsrfOptions } from "./csrf.ts";
|
|
18
19
|
export { ipAllowlist, type IpAllowlistOptions } from "./ip-allowlist.ts";
|
|
19
20
|
export {
|
|
20
21
|
magicLink,
|
|
@@ -67,6 +68,7 @@ export {
|
|
|
67
68
|
type TwoFactorRow,
|
|
68
69
|
type TwoFactorStore,
|
|
69
70
|
} from "./two-factor.ts";
|
|
71
|
+
export type { TwoFactorMethod } from "../auth/two-factor-challenge.ts";
|
|
70
72
|
export {
|
|
71
73
|
assertUsernamePolicy,
|
|
72
74
|
createUsernameStore,
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
* local DX without Mailpit / SMTP.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
completeVerifiedEmailSignIn,
|
|
11
|
+
IdentityError,
|
|
12
|
+
normalizeEmail,
|
|
13
|
+
type IdentityStore,
|
|
14
|
+
type UserIdentityRow,
|
|
15
|
+
} from "../auth/identity.ts";
|
|
10
16
|
import { issueSessionWithScopes } from "../auth/sessions.ts";
|
|
11
17
|
import {
|
|
12
18
|
createVerificationStore,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
z,
|
|
29
35
|
type AuthMethodOptions,
|
|
30
36
|
} from "./auth/shared.ts";
|
|
37
|
+
import { okid } from "../okid.ts";
|
|
31
38
|
|
|
32
39
|
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
33
40
|
const DEFAULT_FROM = "OKE <no-reply@oke.local>";
|
|
@@ -103,22 +110,25 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
103
110
|
})
|
|
104
111
|
: magicLinkTemplate;
|
|
105
112
|
|
|
106
|
-
const
|
|
107
|
-
plane: "user",
|
|
113
|
+
const requestContract = {
|
|
108
114
|
in: z.object({ email: z.string().min(3) }),
|
|
109
115
|
out: z.object({
|
|
110
116
|
ok: z.literal(true),
|
|
111
117
|
devToken: z.string().optional(),
|
|
112
118
|
}),
|
|
113
119
|
errors: { AuthFailed, AuthRateLimited },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const request = flow("auth.requestMagicLink", {
|
|
123
|
+
plane: "user",
|
|
114
124
|
effects: { sends: ["auth-magic-link"] },
|
|
115
125
|
do: async (input, fx) => {
|
|
116
126
|
const email = normalizeEmail(input.email);
|
|
117
127
|
if (!email.includes("@")) return fail("AuthFailed", { reason: "invalid_email" });
|
|
118
|
-
const token = `ml_${
|
|
128
|
+
const token = `ml_${okid()}`;
|
|
119
129
|
const now = runtime.now();
|
|
120
130
|
putVerification(verifications, {
|
|
121
|
-
id:
|
|
131
|
+
id: okid(),
|
|
122
132
|
identifier: `magic:${email}`,
|
|
123
133
|
value: await hashChallenge(token),
|
|
124
134
|
expiresAt: now + ttlMs,
|
|
@@ -138,11 +148,14 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
138
148
|
},
|
|
139
149
|
});
|
|
140
150
|
|
|
141
|
-
const
|
|
142
|
-
plane: "user",
|
|
151
|
+
const verifyContract = {
|
|
143
152
|
in: z.object({ token: z.string().min(1) }),
|
|
144
153
|
out: SessionTokensOut,
|
|
145
154
|
errors: { AuthFailed, AuthRateLimited },
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const verify = flow("auth.verifyMagicLink", {
|
|
158
|
+
plane: "user",
|
|
146
159
|
do: async (input) => {
|
|
147
160
|
const hash = await hashChallenge(input.token);
|
|
148
161
|
const now = runtime.now();
|
|
@@ -168,13 +181,20 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
168
181
|
if (!row) return fail("AuthFailed", { reason: "invalid_credentials" });
|
|
169
182
|
row.consumedAt = now;
|
|
170
183
|
const email = row.identifier.slice("magic:".length);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
let user: UserIdentityRow;
|
|
185
|
+
try {
|
|
186
|
+
({ user } = await completeVerifiedEmailSignIn(identities, runtime.sessions, {
|
|
187
|
+
provider: "magic-link",
|
|
188
|
+
providerAccountId: email,
|
|
189
|
+
email,
|
|
190
|
+
now: () => now,
|
|
191
|
+
}));
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof IdentityError) {
|
|
194
|
+
return fail("AuthFailed", { reason: "invalid_credentials" });
|
|
195
|
+
}
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
178
198
|
const issued = await issueSessionWithScopes(runtime.sessions, runtime.crypto, {
|
|
179
199
|
id: user.id,
|
|
180
200
|
plane: "user",
|
|
@@ -194,6 +214,6 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
|
|
|
194
214
|
.needs("channel")
|
|
195
215
|
.channelTemplate(tmpl)
|
|
196
216
|
.channelCatalog(magicLinkCatalog)
|
|
197
|
-
.binding(bindPublicAuth("/magic-link/request", request, "otp"))
|
|
198
|
-
.binding(bindPublicAuth("/magic-link/verify", verify, "otp"));
|
|
217
|
+
.binding(bindPublicAuth("/magic-link/request", request, "otp", requestContract))
|
|
218
|
+
.binding(bindPublicAuth("/magic-link/verify", verify, "otp", verifyContract));
|
|
199
219
|
}
|
|
@@ -7,6 +7,7 @@ import type { VerificationStore } from "../../auth/verification.ts";
|
|
|
7
7
|
import type { SessionStore } from "../../auth/sessions.ts";
|
|
8
8
|
import { generateCodeVerifier, codeChallengeS256 } from "../../drivers/oauth-shared.ts";
|
|
9
9
|
import type { OAuthDriverId } from "../../drivers/oauth-types.ts";
|
|
10
|
+
import type { BoundaryContract } from "../../kernel/boundary-contract.ts";
|
|
10
11
|
import type { AnyFlowDef } from "../../kernel/flow.ts";
|
|
11
12
|
import { bindAuthHttp } from "../../auth/bindings.ts";
|
|
12
13
|
import { http } from "../auth/shared.ts";
|
|
@@ -97,10 +98,11 @@ export function bindPublicAuthGet(
|
|
|
97
98
|
path: string,
|
|
98
99
|
flowDef: AnyFlowDef,
|
|
99
100
|
gates: readonly Parameters<ReturnType<typeof http.get>["gate"]>[0][] = [],
|
|
101
|
+
contract?: BoundaryContract,
|
|
100
102
|
): Binding {
|
|
101
103
|
return bindAuthHttp(
|
|
102
104
|
http
|
|
103
|
-
.get(path)
|
|
105
|
+
.get(path, contract)
|
|
104
106
|
.public()
|
|
105
107
|
.gate(...gates),
|
|
106
108
|
flowDef,
|
package/src/plugins/oauth.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { bindAuthHttp } from "../auth/bindings.ts";
|
|
31
31
|
import { hashChallenge } from "../auth/verification.ts";
|
|
32
32
|
import { issueSessionWithScopes } from "../auth/sessions.ts";
|
|
33
|
+
import type { BoundaryContract } from "../kernel/boundary-contract.ts";
|
|
33
34
|
import type { Binding } from "../kernel/on.ts";
|
|
34
35
|
import type { Fx } from "../kernel/fx.ts";
|
|
35
36
|
import type { AnyFlowDef } from "../kernel/flow.ts";
|
|
@@ -117,12 +118,14 @@ export function oauth(options: OAuthOptions = {}) {
|
|
|
117
118
|
const now: () => number = () => runtime.now();
|
|
118
119
|
const providers = buildProviders(options);
|
|
119
120
|
|
|
120
|
-
const
|
|
121
|
-
plane: "user",
|
|
121
|
+
const startContract = {
|
|
122
122
|
in: StartIn,
|
|
123
123
|
out: StartOut,
|
|
124
124
|
errors: { AuthFailed, AuthRateLimited },
|
|
125
|
-
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const start = flow("auth.oauthStart", {
|
|
128
|
+
plane: "user",
|
|
126
129
|
do: async (input) => {
|
|
127
130
|
const p = providers.find((c) => c.id === input.provider);
|
|
128
131
|
if (!p) return fail("AuthFailed", { reason: "provider_disabled" });
|
|
@@ -162,11 +165,14 @@ export function oauth(options: OAuthOptions = {}) {
|
|
|
162
165
|
},
|
|
163
166
|
});
|
|
164
167
|
|
|
165
|
-
const
|
|
166
|
-
plane: "user",
|
|
168
|
+
const callbackContract = {
|
|
167
169
|
in: CallbackIn,
|
|
168
170
|
out: SessionTokensOut,
|
|
169
171
|
errors: { AuthFailed, AuthRateLimited },
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const callback = flow("auth.oauthCallback", {
|
|
175
|
+
plane: "user",
|
|
170
176
|
effects: { secrets: callbackSecretRefs(providers) },
|
|
171
177
|
do: async (input, fx) => {
|
|
172
178
|
if (typeof input.error === "string" && input.error.length > 0) {
|
|
@@ -257,12 +263,14 @@ export function oauth(options: OAuthOptions = {}) {
|
|
|
257
263
|
},
|
|
258
264
|
});
|
|
259
265
|
|
|
260
|
-
const
|
|
261
|
-
plane: "user",
|
|
266
|
+
const linkStartContract = {
|
|
262
267
|
in: StartIn,
|
|
263
268
|
out: StartOut,
|
|
264
269
|
errors: { AuthFailed, AuthRateLimited },
|
|
265
|
-
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const linkStart = flow("auth.oauthLinkStart", {
|
|
273
|
+
plane: "user",
|
|
266
274
|
do: async (input, fx) => {
|
|
267
275
|
const currentUserId = fx.auth.userId;
|
|
268
276
|
if (!currentUserId) return fail("AuthFailed", { reason: "unauthenticated" });
|
|
@@ -314,9 +322,9 @@ export function oauth(options: OAuthOptions = {}) {
|
|
|
314
322
|
.flow(callback)
|
|
315
323
|
.flow(linkStart);
|
|
316
324
|
|
|
317
|
-
def.binding(bindPublicAuth("/oauth/:provider/start", start, "otp"));
|
|
318
|
-
for (const b of bindCallbackBothMethods(callback)) def.binding(b);
|
|
319
|
-
def.binding(bindSessionAuth("/oauth/:provider/link", linkStart));
|
|
325
|
+
def.binding(bindPublicAuth("/oauth/:provider/start", start, "otp", startContract));
|
|
326
|
+
for (const b of bindCallbackBothMethods(callback, callbackContract)) def.binding(b);
|
|
327
|
+
def.binding(bindSessionAuth("/oauth/:provider/link", linkStart, linkStartContract));
|
|
320
328
|
|
|
321
329
|
for (const contract of vaultContracts(providers)) def.vault(contract);
|
|
322
330
|
|
|
@@ -335,12 +343,12 @@ const OAUTH_FLOW_TTL_MS = 10 * 60_000;
|
|
|
335
343
|
*
|
|
336
344
|
* @param flowDef - Callback flow
|
|
337
345
|
*/
|
|
338
|
-
function bindCallbackBothMethods(flowDef: AnyFlowDef): Binding[] {
|
|
346
|
+
function bindCallbackBothMethods(flowDef: AnyFlowDef, contract: BoundaryContract): Binding[] {
|
|
339
347
|
const gates = authPublicGates("otp");
|
|
340
|
-
const getBinding = bindPublicAuthGet("/auth/oauth/callback/:provider", flowDef, gates);
|
|
348
|
+
const getBinding = bindPublicAuthGet("/auth/oauth/callback/:provider", flowDef, gates, contract);
|
|
341
349
|
const postBinding = bindAuthHttp(
|
|
342
350
|
http
|
|
343
|
-
.post("/auth/oauth/callback/:provider")
|
|
351
|
+
.post("/auth/oauth/callback/:provider", contract)
|
|
344
352
|
.public()
|
|
345
353
|
.gate(...gates),
|
|
346
354
|
flowDef,
|
package/src/plugins/otp.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { constantTimeEqual } from "../auth/constant-time.ts";
|
|
7
7
|
import {
|
|
8
|
+
completeVerifiedEmailSignIn,
|
|
8
9
|
IdentityError,
|
|
9
10
|
linkOrProvision,
|
|
10
11
|
normalizeEmail,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
z,
|
|
41
42
|
type AuthMethodOptions,
|
|
42
43
|
} from "./auth/shared.ts";
|
|
44
|
+
import { okid } from "../okid.ts";
|
|
43
45
|
|
|
44
46
|
const E164 = /^\+[1-9]\d{7,14}$/;
|
|
45
47
|
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
@@ -238,14 +240,17 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
238
240
|
});
|
|
239
241
|
|
|
240
242
|
if (opts.mode === "provider") {
|
|
241
|
-
const
|
|
242
|
-
plane: "user",
|
|
243
|
+
const requestContract = {
|
|
243
244
|
in: z.object({
|
|
244
245
|
phone: z.string().min(8),
|
|
245
246
|
lang: z.enum(["en", "ar"]).optional(),
|
|
246
247
|
}),
|
|
247
248
|
out: requestOut,
|
|
248
249
|
errors: { AuthFailed, AuthRateLimited },
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const request = flow("auth.requestOtp", {
|
|
253
|
+
plane: "user",
|
|
249
254
|
effects: { sends: ["sms-otp"] },
|
|
250
255
|
do: async (input, fx) => {
|
|
251
256
|
const phone = input.phone.trim();
|
|
@@ -254,7 +259,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
254
259
|
const identifier = `otp:${phone}`;
|
|
255
260
|
invalidateVerifications(verifications, identifier, now);
|
|
256
261
|
|
|
257
|
-
const requestId =
|
|
262
|
+
const requestId = okid();
|
|
258
263
|
await fx.sendOtp({
|
|
259
264
|
to: phone,
|
|
260
265
|
requestId,
|
|
@@ -262,7 +267,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
262
267
|
});
|
|
263
268
|
|
|
264
269
|
putVerification(verifications, {
|
|
265
|
-
id:
|
|
270
|
+
id: okid(),
|
|
266
271
|
identifier,
|
|
267
272
|
value: `provider:${requestId}`,
|
|
268
273
|
expiresAt: now + ttlMs,
|
|
@@ -277,8 +282,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
277
282
|
},
|
|
278
283
|
});
|
|
279
284
|
|
|
280
|
-
const
|
|
281
|
-
plane: "user",
|
|
285
|
+
const verifyContract = {
|
|
282
286
|
in: z.object({
|
|
283
287
|
phone: z.string().min(8),
|
|
284
288
|
otp: z.string().min(4).max(8),
|
|
@@ -286,6 +290,10 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
286
290
|
}),
|
|
287
291
|
out: SessionTokensOut,
|
|
288
292
|
errors: { AuthFailed, AuthRateLimited },
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const verify = flow("auth.verifyOtp", {
|
|
296
|
+
plane: "user",
|
|
289
297
|
effects: { sends: ["sms-otp"] },
|
|
290
298
|
do: async (input, fx) => {
|
|
291
299
|
const phone = input.phone.trim();
|
|
@@ -352,13 +360,12 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
352
360
|
|
|
353
361
|
return plugin("otp", { version: "0.0.1", config: configSnapshot })
|
|
354
362
|
.needs("auth")
|
|
355
|
-
.binding(bindPublicAuth("/otp/request", request, "otp"))
|
|
356
|
-
.binding(bindPublicAuth("/otp/verify", verify, "otp"));
|
|
363
|
+
.binding(bindPublicAuth("/otp/request", request, "otp", requestContract))
|
|
364
|
+
.binding(bindPublicAuth("/otp/verify", verify, "otp", verifyContract));
|
|
357
365
|
}
|
|
358
366
|
|
|
359
367
|
// ── App mode ───────────────────────────────────────────────────────────
|
|
360
|
-
const
|
|
361
|
-
plane: "user",
|
|
368
|
+
const requestContract = {
|
|
362
369
|
in: z.object({
|
|
363
370
|
email: z.string().min(3).optional(),
|
|
364
371
|
phone: z.string().min(8).optional(),
|
|
@@ -367,6 +374,10 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
367
374
|
}),
|
|
368
375
|
out: requestOut,
|
|
369
376
|
errors: { AuthFailed, AuthRateLimited },
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const request = flow("auth.requestOtp", {
|
|
380
|
+
plane: "user",
|
|
370
381
|
effects: { sends: ["auth-otp", "auth-otp-email", "auth-otp-sms", "auth-otp-whatsapp"] },
|
|
371
382
|
do: async (input, fx) => {
|
|
372
383
|
const email = input.email ? normalizeEmail(input.email) : undefined;
|
|
@@ -392,7 +403,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
392
403
|
|
|
393
404
|
const sealed = await sealOtp(secret, otpCode);
|
|
394
405
|
putVerification(verifications, {
|
|
395
|
-
id:
|
|
406
|
+
id: okid(),
|
|
396
407
|
identifier,
|
|
397
408
|
value: await hashChallenge(otpCode),
|
|
398
409
|
expiresAt: now + ttlMs,
|
|
@@ -423,8 +434,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
423
434
|
},
|
|
424
435
|
});
|
|
425
436
|
|
|
426
|
-
const
|
|
427
|
-
plane: "user",
|
|
437
|
+
const resendContract = {
|
|
428
438
|
in: z.object({
|
|
429
439
|
email: z.string().min(3).optional(),
|
|
430
440
|
phone: z.string().min(8).optional(),
|
|
@@ -433,6 +443,10 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
433
443
|
}),
|
|
434
444
|
out: requestOut,
|
|
435
445
|
errors: { AuthFailed, AuthRateLimited },
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const resend = flow("auth.resendOtp", {
|
|
449
|
+
plane: "user",
|
|
436
450
|
effects: { sends: ["auth-otp", "auth-otp-email", "auth-otp-sms", "auth-otp-whatsapp"] },
|
|
437
451
|
do: async (input, fx) => {
|
|
438
452
|
const email = input.email ? normalizeEmail(input.email) : undefined;
|
|
@@ -492,8 +506,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
492
506
|
},
|
|
493
507
|
});
|
|
494
508
|
|
|
495
|
-
const
|
|
496
|
-
plane: "user",
|
|
509
|
+
const verifyContract = {
|
|
497
510
|
in: z.object({
|
|
498
511
|
email: z.string().min(3).optional(),
|
|
499
512
|
phone: z.string().min(8).optional(),
|
|
@@ -501,6 +514,10 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
501
514
|
}),
|
|
502
515
|
out: SessionTokensOut,
|
|
503
516
|
errors: { AuthFailed, AuthRateLimited },
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const verify = flow("auth.verifyOtp", {
|
|
520
|
+
plane: "user",
|
|
504
521
|
do: async (input) => {
|
|
505
522
|
const email = input.email ? normalizeEmail(input.email) : undefined;
|
|
506
523
|
const phone = input.phone?.trim();
|
|
@@ -563,11 +580,10 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
563
580
|
let user: UserIdentityRow;
|
|
564
581
|
try {
|
|
565
582
|
user = (
|
|
566
|
-
await
|
|
583
|
+
await completeVerifiedEmailSignIn(identities, runtime.sessions, {
|
|
567
584
|
provider: "otp",
|
|
568
585
|
providerAccountId: email!,
|
|
569
|
-
email
|
|
570
|
-
emailVerified: true,
|
|
586
|
+
email: email!,
|
|
571
587
|
now: () => now,
|
|
572
588
|
})
|
|
573
589
|
).user;
|
|
@@ -598,7 +614,7 @@ export function otp(opts: OtpOptions): PluginDef {
|
|
|
598
614
|
.channelTemplate(otpSmsTemplate)
|
|
599
615
|
.channelTemplate(otpWhatsappTemplate)
|
|
600
616
|
.channelCatalog(otpCatalog)
|
|
601
|
-
.binding(bindPublicAuth("/otp/request", request, "otp"))
|
|
602
|
-
.binding(bindPublicAuth("/otp/verify", verify, "otp"))
|
|
603
|
-
.binding(bindPublicAuth("/otp/resend", resend, "otp"));
|
|
617
|
+
.binding(bindPublicAuth("/otp/request", request, "otp", requestContract))
|
|
618
|
+
.binding(bindPublicAuth("/otp/verify", verify, "otp", verifyContract))
|
|
619
|
+
.binding(bindPublicAuth("/otp/resend", resend, "otp", resendContract));
|
|
604
620
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Minimal WebAuthn assertion / registration verifier (ES256 / P-256).
|
|
3
3
|
*
|
|
4
4
|
* Validates clientDataJSON (type · challenge · origin), authenticatorData
|
|
5
|
-
* (rpId hash · user-presence), and ECDSA signature over
|
|
5
|
+
* (rpId hash · user-presence · user-verified), and ECDSA signature over
|
|
6
6
|
* `authenticatorData || SHA-256(clientDataJSON)` against a stored SPKI public key.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -92,14 +92,17 @@ export async function verifyWebAuthnCeremony(
|
|
|
92
92
|
const flags = authData[32]!;
|
|
93
93
|
// User Present (bit 0) required.
|
|
94
94
|
if ((flags & 0x01) === 0) return { ok: false, reason: "invalid_credentials" };
|
|
95
|
+
// User Verified (bit 2) required — never accept UV=false (even if RP policy is "preferred").
|
|
96
|
+
if ((flags & 0x04) === 0) return { ok: false, reason: "user_not_verified" };
|
|
95
97
|
|
|
96
98
|
const signCount =
|
|
97
99
|
((authData[33]! << 24) | (authData[34]! << 16) | (authData[35]! << 8) | authData[36]!) >>> 0;
|
|
98
100
|
|
|
99
101
|
if (opts.previousSignCount !== undefined) {
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
// Stored counter non-zero + non-increasing → possible cloned / replayed authenticator.
|
|
103
|
+
// Skip when stored is 0 (synced / non-counter authenticators).
|
|
104
|
+
if (opts.previousSignCount !== 0 && signCount <= opts.previousSignCount) {
|
|
105
|
+
return { ok: false, reason: "cloned_authenticator" };
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -164,12 +167,12 @@ export function b64urlDecode(input: string): Uint8Array<ArrayBuffer> {
|
|
|
164
167
|
*
|
|
165
168
|
* @param rpId - Relying party id
|
|
166
169
|
* @param signCount - Counter
|
|
167
|
-
* @param flags - Flag byte (default UP=
|
|
170
|
+
* @param flags - Flag byte (default UP|UV = 0x05)
|
|
168
171
|
*/
|
|
169
172
|
export async function buildAuthenticatorData(
|
|
170
173
|
rpId: string,
|
|
171
174
|
signCount: number,
|
|
172
|
-
flags =
|
|
175
|
+
flags = 0x05,
|
|
173
176
|
): Promise<Uint8Array> {
|
|
174
177
|
const rpHash = new Uint8Array(
|
|
175
178
|
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rpId)),
|