okengine 0.10.2 → 0.11.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 +76 -10
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -13
- package/site/content/docs/elements/channel.mdx +9 -13
- package/site/content/docs/elements/clock.mdx +8 -14
- package/site/content/docs/elements/flow.mdx +46 -54
- package/site/content/docs/elements/gate.mdx +6 -11
- package/site/content/docs/elements/signal.mdx +2 -8
- package/site/content/docs/elements/store.mdx +79 -93
- package/site/content/docs/elements/vault.mdx +89 -76
- package/site/content/docs/get-started/basic-usage.mdx +8 -14
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +3 -4
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +14 -7
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +30 -16
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +8 -9
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +246 -114
- package/src/cli/dev.ts +153 -205
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +142 -9
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +44 -10
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +35 -29
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +29 -11
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +115 -0
- package/src/config/driver-defaults.ts +156 -0
- package/src/config/index.ts +246 -131
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +163 -0
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +5 -4
- package/src/kernel/boot-bind/clock.ts +5 -5
- package/src/kernel/boot-bind/gate.ts +6 -7
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +5 -5
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +55 -37
- package/src/kernel/boot-bind/store.ts +24 -34
- package/src/kernel/boot-bind/vault.ts +19 -4
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Durable operator plane for Console —
|
|
2
|
+
* Durable operator plane for Console — Postgres schema `oke_console` + secret file.
|
|
3
|
+
*
|
|
4
|
+
* Same database as the app (`DATABASE_URL` / `OKE_STORE_SQL_URL`):
|
|
5
|
+
* - `public` — application + shared runtime tables
|
|
6
|
+
* - `oke_console` — Console operators / sessions (never mixed into `public`)
|
|
7
|
+
*
|
|
8
|
+
* When no Postgres URL is set (unit tests), falls back to PGlite under
|
|
9
|
+
* `.oke/console-pg` so reopen durability still works without Docker.
|
|
3
10
|
*
|
|
4
11
|
* Spec: wizard closes permanently once the first operator exists (console §2.5).
|
|
5
12
|
* Claim codes stay ephemeral; operators, sessions, and the signing secret must
|
|
@@ -8,7 +15,6 @@
|
|
|
8
15
|
|
|
9
16
|
import { mkdirSync } from "node:fs";
|
|
10
17
|
import { join } from "node:path";
|
|
11
|
-
import { Database } from "bun:sqlite";
|
|
12
18
|
import { createOperatorStore, type OperatorStore } from "../../auth/operator.ts";
|
|
13
19
|
import { createSessionStore, type SessionStore } from "../../auth/sessions.ts";
|
|
14
20
|
import type {
|
|
@@ -19,11 +25,19 @@ import type {
|
|
|
19
25
|
SessionRow,
|
|
20
26
|
} from "../../auth/tables.ts";
|
|
21
27
|
import { AUTH_TABLES } from "../../auth/tables.ts";
|
|
28
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
22
29
|
|
|
23
30
|
/** Relative paths under project cwd. */
|
|
24
31
|
export const CONSOLE_OKE_DIR = ".oke";
|
|
25
|
-
|
|
32
|
+
/** Signing secret file (still local — not a DB row). */
|
|
26
33
|
export const CONSOLE_SECRET_NAME = "console.secret";
|
|
34
|
+
/** PGlite datadir when no Postgres URL is configured. */
|
|
35
|
+
export const CONSOLE_PGLITE_DIR = "console-pg";
|
|
36
|
+
/**
|
|
37
|
+
* Postgres schema for Console operator-plane tables.
|
|
38
|
+
* App domain tables stay in `public` on the same database.
|
|
39
|
+
*/
|
|
40
|
+
export const CONSOLE_PG_SCHEMA = "oke_console";
|
|
27
41
|
|
|
28
42
|
/** Opened Console persistence handle. */
|
|
29
43
|
export interface ConsolePersistence {
|
|
@@ -34,11 +48,23 @@ export interface ConsolePersistence {
|
|
|
34
48
|
/** Hydrated session + refresh Maps. */
|
|
35
49
|
readonly sessions: SessionStore;
|
|
36
50
|
/** Persist (or update) one operator + credential + roles/sso. */
|
|
37
|
-
readonly persistOperator: (operatorId: string) => void
|
|
51
|
+
readonly persistOperator: (operatorId: string) => Promise<void>;
|
|
38
52
|
/** Persist the full session store (issue / revoke). */
|
|
39
|
-
readonly persistSessions: () => void
|
|
40
|
-
/** Close the
|
|
41
|
-
readonly close: () => void
|
|
53
|
+
readonly persistSessions: () => Promise<void>;
|
|
54
|
+
/** Close the SQL connection. */
|
|
55
|
+
readonly close: () => Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Options for {@link openConsolePersistence}. */
|
|
59
|
+
export interface OpenConsolePersistenceOptions {
|
|
60
|
+
/** Optional `OKE_CONSOLE_SECRET` override. */
|
|
61
|
+
readonly envSecret?: string;
|
|
62
|
+
/** SQL URL override (`DATABASE_URL` / `OKE_STORE_SQL_URL` otherwise). */
|
|
63
|
+
readonly url?: string;
|
|
64
|
+
/** Injected connection (tests). */
|
|
65
|
+
readonly connection?: SqlConnection;
|
|
66
|
+
/** Injected env map (tests). */
|
|
67
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
/**
|
|
@@ -48,18 +74,27 @@ export interface ConsolePersistence {
|
|
|
48
74
|
*/
|
|
49
75
|
export function consoleOkePaths(cwd: string): {
|
|
50
76
|
readonly dir: string;
|
|
51
|
-
readonly sqlite: string;
|
|
52
77
|
readonly secret: string;
|
|
78
|
+
readonly pglite: string;
|
|
53
79
|
} {
|
|
54
80
|
const dir = join(cwd, CONSOLE_OKE_DIR);
|
|
55
81
|
mkdirSync(dir, { recursive: true });
|
|
56
82
|
return {
|
|
57
83
|
dir,
|
|
58
|
-
sqlite: join(dir, CONSOLE_SQLITE_NAME),
|
|
59
84
|
secret: join(dir, CONSOLE_SECRET_NAME),
|
|
85
|
+
pglite: join(dir, CONSOLE_PGLITE_DIR),
|
|
60
86
|
};
|
|
61
87
|
}
|
|
62
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Qualify a table name inside {@link CONSOLE_PG_SCHEMA}.
|
|
91
|
+
*
|
|
92
|
+
* @param table - Bare table name (`oke_operators`, …)
|
|
93
|
+
*/
|
|
94
|
+
export function consoleTable(table: string): string {
|
|
95
|
+
return `"${CONSOLE_PG_SCHEMA}"."${table}"`;
|
|
96
|
+
}
|
|
97
|
+
|
|
63
98
|
/**
|
|
64
99
|
* Load or create a stable Console signing secret under `.oke/console.secret`.
|
|
65
100
|
*
|
|
@@ -85,192 +120,230 @@ export async function resolveConsoleSecret(cwd: string, envSecret?: string): Pro
|
|
|
85
120
|
* Open Console operator DB, migrate schema, hydrate Maps.
|
|
86
121
|
*
|
|
87
122
|
* @param cwd - Project root
|
|
88
|
-
* @param options -
|
|
123
|
+
* @param options - Secret / URL / connection overrides
|
|
89
124
|
*/
|
|
90
125
|
export async function openConsolePersistence(
|
|
91
126
|
cwd: string,
|
|
92
|
-
options:
|
|
127
|
+
options: OpenConsolePersistenceOptions = {},
|
|
93
128
|
): Promise<ConsolePersistence> {
|
|
94
|
-
const paths = consoleOkePaths(cwd);
|
|
95
129
|
const secret = await resolveConsoleSecret(cwd, options.envSecret);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
130
|
+
const env = options.env ?? process.env;
|
|
131
|
+
const url = options.url ?? env.DATABASE_URL ?? env.OKE_STORE_SQL_URL;
|
|
132
|
+
const owned = options.connection === undefined;
|
|
133
|
+
const sql = options.connection ?? (await connectConsoleSql(cwd, url));
|
|
134
|
+
|
|
135
|
+
await migrateOperatorSchema(sql);
|
|
136
|
+
const operators = await loadOperatorStore(sql);
|
|
137
|
+
const sessions = await loadSessionStore(sql);
|
|
101
138
|
|
|
102
139
|
return {
|
|
103
140
|
secret,
|
|
104
141
|
operators,
|
|
105
142
|
sessions,
|
|
106
|
-
persistOperator(operatorId: string) {
|
|
107
|
-
persistOperator(
|
|
143
|
+
async persistOperator(operatorId: string) {
|
|
144
|
+
await persistOperator(sql, operators, operatorId);
|
|
108
145
|
},
|
|
109
|
-
persistSessions() {
|
|
110
|
-
persistSessions(
|
|
146
|
+
async persistSessions() {
|
|
147
|
+
await persistSessions(sql, sessions);
|
|
111
148
|
},
|
|
112
|
-
close() {
|
|
113
|
-
|
|
149
|
+
async close() {
|
|
150
|
+
if (owned) await sql.close();
|
|
114
151
|
},
|
|
115
152
|
};
|
|
116
153
|
}
|
|
117
154
|
|
|
118
155
|
/**
|
|
119
|
-
*
|
|
156
|
+
* Open Postgres (shared app URL) or PGlite under `.oke/console-pg`.
|
|
157
|
+
*
|
|
158
|
+
* @param cwd - Project root
|
|
159
|
+
* @param url - Resolved SQL URL, when configured
|
|
160
|
+
*/
|
|
161
|
+
async function connectConsoleSql(cwd: string, url: string | undefined): Promise<SqlConnection> {
|
|
162
|
+
if (url !== undefined && /^postgres(ql)?:\/\//.test(url)) {
|
|
163
|
+
const { connectPostgres } = await import("../../drivers/postgres.ts");
|
|
164
|
+
return connectPostgres({ url });
|
|
165
|
+
}
|
|
166
|
+
const { connectPglite } = await import("../../drivers/pglite.ts");
|
|
167
|
+
const paths = consoleOkePaths(cwd);
|
|
168
|
+
// Explicit non-postgres URL (tests) wins; otherwise durable PGlite datadir.
|
|
169
|
+
if (url !== undefined && url.length > 0) {
|
|
170
|
+
return connectPglite({ url });
|
|
171
|
+
}
|
|
172
|
+
return connectPglite({ url: paths.pglite });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create `oke_console` schema + operator tables if missing.
|
|
120
177
|
*
|
|
121
|
-
* @param
|
|
178
|
+
* @param sql - SQL connection
|
|
122
179
|
*/
|
|
123
|
-
export function migrateOperatorSchema(
|
|
124
|
-
|
|
125
|
-
|
|
180
|
+
export async function migrateOperatorSchema(sql: SqlConnection): Promise<void> {
|
|
181
|
+
await sql.exec(`CREATE SCHEMA IF NOT EXISTS "${CONSOLE_PG_SCHEMA}"`);
|
|
182
|
+
const ops = consoleTable(AUTH_TABLES.operators);
|
|
183
|
+
const creds = consoleTable(AUTH_TABLES.operatorCredentials);
|
|
184
|
+
const sso = consoleTable(AUTH_TABLES.operatorSsoLinks);
|
|
185
|
+
const roles = consoleTable(AUTH_TABLES.operatorRoles);
|
|
186
|
+
const sessions = consoleTable(AUTH_TABLES.sessions);
|
|
187
|
+
const refresh = consoleTable(AUTH_TABLES.refreshTokens);
|
|
188
|
+
|
|
189
|
+
await sql.exec(`
|
|
190
|
+
CREATE TABLE IF NOT EXISTS ${ops} (
|
|
126
191
|
id TEXT PRIMARY KEY NOT NULL,
|
|
127
192
|
email TEXT NOT NULL UNIQUE,
|
|
128
193
|
name TEXT NOT NULL,
|
|
129
194
|
status TEXT NOT NULL,
|
|
130
|
-
mfa_enabled
|
|
195
|
+
mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
131
196
|
invited_by TEXT,
|
|
132
|
-
last_seen_at
|
|
197
|
+
last_seen_at BIGINT
|
|
133
198
|
);
|
|
134
|
-
|
|
199
|
+
`);
|
|
200
|
+
await sql.exec(`
|
|
201
|
+
CREATE TABLE IF NOT EXISTS ${creds} (
|
|
135
202
|
operator_id TEXT PRIMARY KEY NOT NULL,
|
|
136
203
|
password_hash TEXT NOT NULL,
|
|
137
|
-
login_enabled
|
|
138
|
-
FOREIGN KEY (operator_id) REFERENCES ${
|
|
204
|
+
login_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
205
|
+
FOREIGN KEY (operator_id) REFERENCES ${ops}(id)
|
|
139
206
|
);
|
|
140
|
-
|
|
207
|
+
`);
|
|
208
|
+
await sql.exec(`
|
|
209
|
+
CREATE TABLE IF NOT EXISTS ${sso} (
|
|
141
210
|
operator_id TEXT NOT NULL,
|
|
142
211
|
provider TEXT NOT NULL,
|
|
143
212
|
subject TEXT NOT NULL,
|
|
144
213
|
PRIMARY KEY (operator_id, provider, subject),
|
|
145
|
-
FOREIGN KEY (operator_id) REFERENCES ${
|
|
214
|
+
FOREIGN KEY (operator_id) REFERENCES ${ops}(id)
|
|
146
215
|
);
|
|
147
|
-
|
|
216
|
+
`);
|
|
217
|
+
await sql.exec(`
|
|
218
|
+
CREATE TABLE IF NOT EXISTS ${roles} (
|
|
148
219
|
operator_id TEXT NOT NULL,
|
|
149
220
|
role TEXT NOT NULL,
|
|
150
221
|
PRIMARY KEY (operator_id, role),
|
|
151
|
-
FOREIGN KEY (operator_id) REFERENCES ${
|
|
222
|
+
FOREIGN KEY (operator_id) REFERENCES ${ops}(id)
|
|
152
223
|
);
|
|
153
|
-
|
|
224
|
+
`);
|
|
225
|
+
await sql.exec(`
|
|
226
|
+
CREATE TABLE IF NOT EXISTS ${sessions} (
|
|
154
227
|
id TEXT PRIMARY KEY NOT NULL,
|
|
155
228
|
plane TEXT NOT NULL,
|
|
156
229
|
principal_id TEXT NOT NULL,
|
|
157
230
|
family_id TEXT NOT NULL,
|
|
158
|
-
revoked_at
|
|
159
|
-
created_at
|
|
160
|
-
expires_at
|
|
161
|
-
last_active_at
|
|
231
|
+
revoked_at BIGINT,
|
|
232
|
+
created_at BIGINT NOT NULL,
|
|
233
|
+
expires_at BIGINT NOT NULL,
|
|
234
|
+
last_active_at BIGINT,
|
|
162
235
|
scopes TEXT NOT NULL DEFAULT '[]',
|
|
163
236
|
audience TEXT
|
|
164
237
|
);
|
|
165
|
-
|
|
238
|
+
`);
|
|
239
|
+
await sql.exec(`
|
|
240
|
+
CREATE TABLE IF NOT EXISTS ${refresh} (
|
|
166
241
|
id TEXT PRIMARY KEY NOT NULL,
|
|
167
242
|
session_id TEXT NOT NULL,
|
|
168
243
|
family_id TEXT NOT NULL,
|
|
169
244
|
hash TEXT NOT NULL,
|
|
170
|
-
expires_at
|
|
171
|
-
used_at
|
|
172
|
-
revoked_at
|
|
245
|
+
expires_at BIGINT NOT NULL,
|
|
246
|
+
used_at BIGINT,
|
|
247
|
+
revoked_at BIGINT
|
|
173
248
|
);
|
|
174
249
|
`);
|
|
175
|
-
ensureSessionColumns(
|
|
250
|
+
await ensureSessionColumns(sql);
|
|
176
251
|
}
|
|
177
252
|
|
|
178
253
|
/**
|
|
179
|
-
* Add session columns when opening an older
|
|
254
|
+
* Add session columns when opening an older Console schema.
|
|
180
255
|
*
|
|
181
|
-
* @param
|
|
256
|
+
* @param sql - Open connection
|
|
182
257
|
*/
|
|
183
|
-
function ensureSessionColumns(
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
258
|
+
async function ensureSessionColumns(sql: SqlConnection): Promise<void> {
|
|
259
|
+
const sessions = consoleTable(AUTH_TABLES.sessions);
|
|
260
|
+
await sql.exec(`ALTER TABLE ${sessions} ADD COLUMN IF NOT EXISTS last_active_at BIGINT`);
|
|
261
|
+
await sql.exec(
|
|
262
|
+
`ALTER TABLE ${sessions} ADD COLUMN IF NOT EXISTS scopes TEXT NOT NULL DEFAULT '[]'`,
|
|
263
|
+
);
|
|
264
|
+
await sql.exec(`ALTER TABLE ${sessions} ADD COLUMN IF NOT EXISTS audience TEXT`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Coerce a SQL boolean / 0|1 cell to boolean.
|
|
269
|
+
*
|
|
270
|
+
* @param value - Driver cell
|
|
271
|
+
*/
|
|
272
|
+
function asBool(value: unknown): boolean {
|
|
273
|
+
return value === true || value === 1 || value === "1" || value === "t" || value === "true";
|
|
197
274
|
}
|
|
198
275
|
|
|
199
276
|
/**
|
|
200
|
-
*
|
|
277
|
+
* Coerce a SQL numeric cell to number | null.
|
|
201
278
|
*
|
|
202
|
-
* @param
|
|
279
|
+
* @param value - Driver cell
|
|
203
280
|
*/
|
|
204
|
-
|
|
281
|
+
function asNum(value: unknown): number | null {
|
|
282
|
+
if (value === null || value === undefined) return null;
|
|
283
|
+
if (typeof value === "number") return value;
|
|
284
|
+
if (typeof value === "bigint") return Number(value);
|
|
285
|
+
const n = Number(value);
|
|
286
|
+
return Number.isFinite(n) ? n : null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Hydrate a {@link SessionStore} from SQL.
|
|
291
|
+
*
|
|
292
|
+
* @param sql - Open connection
|
|
293
|
+
*/
|
|
294
|
+
export async function loadSessionStore(sql: SqlConnection): Promise<SessionStore> {
|
|
205
295
|
const store = createSessionStore();
|
|
296
|
+
const sessions = consoleTable(AUTH_TABLES.sessions);
|
|
297
|
+
const refresh = consoleTable(AUTH_TABLES.refreshTokens);
|
|
206
298
|
|
|
207
|
-
const sessionRows =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
)
|
|
212
|
-
.all() as Array<{
|
|
213
|
-
id: string;
|
|
214
|
-
plane: SessionRow["plane"];
|
|
215
|
-
principal_id: string;
|
|
216
|
-
family_id: string;
|
|
217
|
-
revoked_at: number | null;
|
|
218
|
-
created_at: number;
|
|
219
|
-
expires_at: number;
|
|
220
|
-
last_active_at: number | null;
|
|
221
|
-
scopes: string | null;
|
|
222
|
-
audience: string | null;
|
|
223
|
-
}>;
|
|
299
|
+
const sessionRows = await sql.query(
|
|
300
|
+
`SELECT id, plane, principal_id, family_id, revoked_at, created_at, expires_at, last_active_at, scopes, audience
|
|
301
|
+
FROM ${sessions}`,
|
|
302
|
+
);
|
|
224
303
|
|
|
225
304
|
for (const row of sessionRows) {
|
|
226
305
|
let scopes: string[] = [];
|
|
227
|
-
|
|
306
|
+
const scopesRaw = row["scopes"];
|
|
307
|
+
if (typeof scopesRaw === "string" && scopesRaw.length > 0) {
|
|
228
308
|
try {
|
|
229
|
-
const parsed = JSON.parse(
|
|
309
|
+
const parsed = JSON.parse(scopesRaw) as unknown;
|
|
230
310
|
if (Array.isArray(parsed)) scopes = parsed.map(String);
|
|
231
311
|
} catch {
|
|
232
312
|
scopes = [];
|
|
233
313
|
}
|
|
234
314
|
}
|
|
315
|
+
const createdAt = asNum(row["created_at"]) ?? 0;
|
|
235
316
|
const session: SessionRow = {
|
|
236
|
-
id: row
|
|
237
|
-
plane: row
|
|
238
|
-
principalId: row
|
|
239
|
-
familyId: row
|
|
240
|
-
revokedAt: row
|
|
241
|
-
createdAt
|
|
242
|
-
expiresAt: row
|
|
243
|
-
lastActiveAt: row
|
|
317
|
+
id: String(row["id"]),
|
|
318
|
+
plane: row["plane"] as SessionRow["plane"],
|
|
319
|
+
principalId: String(row["principal_id"]),
|
|
320
|
+
familyId: String(row["family_id"]),
|
|
321
|
+
revokedAt: asNum(row["revoked_at"]),
|
|
322
|
+
createdAt,
|
|
323
|
+
expiresAt: asNum(row["expires_at"]) ?? 0,
|
|
324
|
+
lastActiveAt: asNum(row["last_active_at"]) ?? createdAt,
|
|
244
325
|
scopes,
|
|
245
|
-
...(row
|
|
326
|
+
...(typeof row["audience"] === "string" && row["audience"].length > 0
|
|
327
|
+
? { audience: row["audience"] }
|
|
328
|
+
: {}),
|
|
246
329
|
};
|
|
247
330
|
store.sessions.set(session.id, session);
|
|
248
331
|
}
|
|
249
332
|
|
|
250
|
-
const refreshRows =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
.all() as Array<{
|
|
256
|
-
id: string;
|
|
257
|
-
session_id: string;
|
|
258
|
-
family_id: string;
|
|
259
|
-
hash: string;
|
|
260
|
-
expires_at: number;
|
|
261
|
-
used_at: number | null;
|
|
262
|
-
revoked_at: number | null;
|
|
263
|
-
}>;
|
|
333
|
+
const refreshRows = await sql.query(
|
|
334
|
+
`SELECT id, session_id, family_id, hash, expires_at, used_at, revoked_at
|
|
335
|
+
FROM ${refresh}`,
|
|
336
|
+
);
|
|
264
337
|
|
|
265
338
|
for (const row of refreshRows) {
|
|
266
339
|
const token: RefreshTokenRow = {
|
|
267
|
-
id: row
|
|
268
|
-
sessionId: row
|
|
269
|
-
familyId: row
|
|
270
|
-
hash: row
|
|
271
|
-
expiresAt: row
|
|
272
|
-
usedAt: row
|
|
273
|
-
revokedAt: row
|
|
340
|
+
id: String(row["id"]),
|
|
341
|
+
sessionId: String(row["session_id"]),
|
|
342
|
+
familyId: String(row["family_id"]),
|
|
343
|
+
hash: String(row["hash"]),
|
|
344
|
+
expiresAt: asNum(row["expires_at"]) ?? 0,
|
|
345
|
+
usedAt: asNum(row["used_at"]),
|
|
346
|
+
revokedAt: asNum(row["revoked_at"]),
|
|
274
347
|
};
|
|
275
348
|
store.refresh.set(token.id, token);
|
|
276
349
|
}
|
|
@@ -281,133 +354,117 @@ export function loadSessionStore(db: Database): SessionStore {
|
|
|
281
354
|
/**
|
|
282
355
|
* Replace session tables with the in-memory store snapshot.
|
|
283
356
|
*
|
|
284
|
-
* @param
|
|
357
|
+
* @param sql - Open connection
|
|
285
358
|
* @param store - In-memory session store
|
|
286
359
|
*/
|
|
287
|
-
export function persistSessions(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
VALUES ($id, $plane, $principal, $family, $revoked, $created, $expires, $lastActive, $scopes, $audience)`,
|
|
295
|
-
);
|
|
360
|
+
export async function persistSessions(sql: SqlConnection, store: SessionStore): Promise<void> {
|
|
361
|
+
const sessions = consoleTable(AUTH_TABLES.sessions);
|
|
362
|
+
const refresh = consoleTable(AUTH_TABLES.refreshTokens);
|
|
363
|
+
|
|
364
|
+
await sql.exec(`DELETE FROM ${refresh}`);
|
|
365
|
+
await sql.exec(`DELETE FROM ${sessions}`);
|
|
366
|
+
|
|
296
367
|
for (const session of store.sessions.values()) {
|
|
297
|
-
|
|
298
|
-
$
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
368
|
+
await sql.exec(
|
|
369
|
+
`INSERT INTO ${sessions}
|
|
370
|
+
(id, plane, principal_id, family_id, revoked_at, created_at, expires_at, last_active_at, scopes, audience)
|
|
371
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
372
|
+
[
|
|
373
|
+
session.id,
|
|
374
|
+
session.plane,
|
|
375
|
+
session.principalId,
|
|
376
|
+
session.familyId,
|
|
377
|
+
session.revokedAt,
|
|
378
|
+
session.createdAt,
|
|
379
|
+
session.expiresAt,
|
|
380
|
+
session.lastActiveAt,
|
|
381
|
+
JSON.stringify(session.scopes ?? []),
|
|
382
|
+
session.audience ?? null,
|
|
383
|
+
],
|
|
384
|
+
);
|
|
309
385
|
}
|
|
310
386
|
|
|
311
|
-
const insertRefresh = db.query(
|
|
312
|
-
`INSERT INTO ${AUTH_TABLES.refreshTokens}
|
|
313
|
-
(id, session_id, family_id, hash, expires_at, used_at, revoked_at)
|
|
314
|
-
VALUES ($id, $session, $family, $hash, $expires, $used, $revoked)`,
|
|
315
|
-
);
|
|
316
387
|
for (const token of store.refresh.values()) {
|
|
317
|
-
|
|
318
|
-
$
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
388
|
+
await sql.exec(
|
|
389
|
+
`INSERT INTO ${refresh}
|
|
390
|
+
(id, session_id, family_id, hash, expires_at, used_at, revoked_at)
|
|
391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
392
|
+
[
|
|
393
|
+
token.id,
|
|
394
|
+
token.sessionId,
|
|
395
|
+
token.familyId,
|
|
396
|
+
token.hash,
|
|
397
|
+
token.expiresAt,
|
|
398
|
+
token.usedAt,
|
|
399
|
+
token.revokedAt,
|
|
400
|
+
],
|
|
401
|
+
);
|
|
326
402
|
}
|
|
327
403
|
}
|
|
328
404
|
|
|
329
405
|
/**
|
|
330
|
-
* Hydrate an {@link OperatorStore} from
|
|
406
|
+
* Hydrate an {@link OperatorStore} from SQL.
|
|
331
407
|
*
|
|
332
|
-
* @param
|
|
408
|
+
* @param sql - Open connection
|
|
333
409
|
*/
|
|
334
|
-
export function loadOperatorStore(
|
|
410
|
+
export async function loadOperatorStore(sql: SqlConnection): Promise<OperatorStore> {
|
|
335
411
|
const store = createOperatorStore();
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
name: string;
|
|
346
|
-
status: OperatorRow["status"];
|
|
347
|
-
mfa_enabled: number;
|
|
348
|
-
invited_by: string | null;
|
|
349
|
-
last_seen_at: number | null;
|
|
350
|
-
}>;
|
|
412
|
+
const ops = consoleTable(AUTH_TABLES.operators);
|
|
413
|
+
const creds = consoleTable(AUTH_TABLES.operatorCredentials);
|
|
414
|
+
const sso = consoleTable(AUTH_TABLES.operatorSsoLinks);
|
|
415
|
+
const roles = consoleTable(AUTH_TABLES.operatorRoles);
|
|
416
|
+
|
|
417
|
+
const opRows = await sql.query(
|
|
418
|
+
`SELECT id, email, name, status, mfa_enabled, invited_by, last_seen_at
|
|
419
|
+
FROM ${ops}`,
|
|
420
|
+
);
|
|
351
421
|
|
|
352
422
|
for (const row of opRows) {
|
|
353
|
-
store.operators.set(row
|
|
354
|
-
id: row
|
|
355
|
-
email: row
|
|
356
|
-
name: row
|
|
357
|
-
status: row
|
|
358
|
-
mfaEnabled: row
|
|
359
|
-
invitedBy: row
|
|
360
|
-
lastSeenAt: row
|
|
423
|
+
store.operators.set(String(row["id"]), {
|
|
424
|
+
id: String(row["id"]),
|
|
425
|
+
email: String(row["email"]),
|
|
426
|
+
name: String(row["name"]),
|
|
427
|
+
status: row["status"] as OperatorRow["status"],
|
|
428
|
+
mfaEnabled: asBool(row["mfa_enabled"]),
|
|
429
|
+
invitedBy: typeof row["invited_by"] === "string" ? row["invited_by"] : null,
|
|
430
|
+
lastSeenAt: asNum(row["last_seen_at"]),
|
|
361
431
|
});
|
|
362
432
|
}
|
|
363
433
|
|
|
364
|
-
const credRows =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
369
|
-
.all() as Array<{
|
|
370
|
-
operator_id: string;
|
|
371
|
-
password_hash: string;
|
|
372
|
-
login_enabled: number;
|
|
373
|
-
}>;
|
|
434
|
+
const credRows = await sql.query(
|
|
435
|
+
`SELECT operator_id, password_hash, login_enabled
|
|
436
|
+
FROM ${creds}`,
|
|
437
|
+
);
|
|
374
438
|
|
|
375
439
|
for (const row of credRows) {
|
|
376
440
|
const cred: OperatorCredentialRow = {
|
|
377
|
-
operatorId: row
|
|
378
|
-
passwordHash: row
|
|
379
|
-
loginEnabled: row
|
|
441
|
+
operatorId: String(row["operator_id"]),
|
|
442
|
+
passwordHash: String(row["password_hash"]),
|
|
443
|
+
loginEnabled: asBool(row["login_enabled"]),
|
|
380
444
|
};
|
|
381
|
-
store.credentials.set(
|
|
445
|
+
store.credentials.set(cred.operatorId, cred);
|
|
382
446
|
}
|
|
383
447
|
|
|
384
|
-
const ssoRows =
|
|
385
|
-
.query(`SELECT operator_id, provider, subject FROM ${AUTH_TABLES.operatorSsoLinks}`)
|
|
386
|
-
.all() as Array<{
|
|
387
|
-
operator_id: string;
|
|
388
|
-
provider: string;
|
|
389
|
-
subject: string;
|
|
390
|
-
}>;
|
|
448
|
+
const ssoRows = await sql.query(`SELECT operator_id, provider, subject FROM ${sso}`);
|
|
391
449
|
|
|
392
450
|
for (const row of ssoRows) {
|
|
393
451
|
const link: OperatorSsoLinkRow = {
|
|
394
|
-
operatorId: row
|
|
395
|
-
provider: row
|
|
396
|
-
subject: row
|
|
452
|
+
operatorId: String(row["operator_id"]),
|
|
453
|
+
provider: String(row["provider"]),
|
|
454
|
+
subject: String(row["subject"]),
|
|
397
455
|
};
|
|
398
|
-
const list = store.ssoLinks.get(
|
|
456
|
+
const list = store.ssoLinks.get(link.operatorId) ?? [];
|
|
399
457
|
list.push(link);
|
|
400
|
-
store.ssoLinks.set(
|
|
458
|
+
store.ssoLinks.set(link.operatorId, list);
|
|
401
459
|
}
|
|
402
460
|
|
|
403
|
-
const roleRows =
|
|
404
|
-
.query(`SELECT operator_id, role FROM ${AUTH_TABLES.operatorRoles}`)
|
|
405
|
-
.all() as Array<{ operator_id: string; role: string }>;
|
|
461
|
+
const roleRows = await sql.query(`SELECT operator_id, role FROM ${roles}`);
|
|
406
462
|
|
|
407
463
|
for (const row of roleRows) {
|
|
408
|
-
const
|
|
409
|
-
list.
|
|
410
|
-
|
|
464
|
+
const operatorId = String(row["operator_id"]);
|
|
465
|
+
const list = store.roles.get(operatorId) ?? [];
|
|
466
|
+
list.push(String(row["role"]));
|
|
467
|
+
store.roles.set(operatorId, list);
|
|
411
468
|
}
|
|
412
469
|
|
|
413
470
|
return store;
|
|
@@ -416,11 +473,15 @@ export function loadOperatorStore(db: Database): OperatorStore {
|
|
|
416
473
|
/**
|
|
417
474
|
* Upsert one operator (and related rows) from the in-memory store.
|
|
418
475
|
*
|
|
419
|
-
* @param
|
|
476
|
+
* @param sql - Open connection
|
|
420
477
|
* @param store - In-memory store
|
|
421
478
|
* @param operatorId - Operator id
|
|
422
479
|
*/
|
|
423
|
-
export function persistOperator(
|
|
480
|
+
export async function persistOperator(
|
|
481
|
+
sql: SqlConnection,
|
|
482
|
+
store: OperatorStore,
|
|
483
|
+
operatorId: string,
|
|
484
|
+
): Promise<void> {
|
|
424
485
|
const op = store.operators.get(operatorId);
|
|
425
486
|
if (!op) {
|
|
426
487
|
throw new Error(`oke console: unknown operator ${operatorId}`);
|
|
@@ -430,65 +491,46 @@ export function persistOperator(db: Database, store: OperatorStore, operatorId:
|
|
|
430
491
|
throw new Error(`oke console: missing credential for ${operatorId}`);
|
|
431
492
|
}
|
|
432
493
|
|
|
433
|
-
const
|
|
434
|
-
|
|
494
|
+
const ops = consoleTable(AUTH_TABLES.operators);
|
|
495
|
+
const creds = consoleTable(AUTH_TABLES.operatorCredentials);
|
|
496
|
+
const sso = consoleTable(AUTH_TABLES.operatorSsoLinks);
|
|
497
|
+
const roles = consoleTable(AUTH_TABLES.operatorRoles);
|
|
498
|
+
|
|
499
|
+
await sql.exec(
|
|
500
|
+
`INSERT INTO ${ops}
|
|
435
501
|
(id, email, name, status, mfa_enabled, invited_by, last_seen_at)
|
|
436
|
-
VALUES (
|
|
437
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
438
|
-
email =
|
|
439
|
-
name =
|
|
440
|
-
status =
|
|
441
|
-
mfa_enabled =
|
|
442
|
-
invited_by =
|
|
443
|
-
last_seen_at =
|
|
502
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
503
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
504
|
+
email = EXCLUDED.email,
|
|
505
|
+
name = EXCLUDED.name,
|
|
506
|
+
status = EXCLUDED.status,
|
|
507
|
+
mfa_enabled = EXCLUDED.mfa_enabled,
|
|
508
|
+
invited_by = EXCLUDED.invited_by,
|
|
509
|
+
last_seen_at = EXCLUDED.last_seen_at`,
|
|
510
|
+
[op.id, op.email, op.name, op.status, op.mfaEnabled, op.invitedBy, op.lastSeenAt],
|
|
444
511
|
);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
$
|
|
448
|
-
$name: op.name,
|
|
449
|
-
$status: op.status,
|
|
450
|
-
$mfa: op.mfaEnabled ? 1 : 0,
|
|
451
|
-
$invited: op.invitedBy,
|
|
452
|
-
$seen: op.lastSeenAt,
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
const upsertCred = db.query(
|
|
456
|
-
`INSERT INTO ${AUTH_TABLES.operatorCredentials}
|
|
512
|
+
|
|
513
|
+
await sql.exec(
|
|
514
|
+
`INSERT INTO ${creds}
|
|
457
515
|
(operator_id, password_hash, login_enabled)
|
|
458
|
-
VALUES (
|
|
459
|
-
ON CONFLICT(operator_id) DO UPDATE SET
|
|
460
|
-
password_hash =
|
|
461
|
-
login_enabled =
|
|
462
|
-
|
|
463
|
-
upsertCred.run({
|
|
464
|
-
$id: cred.operatorId,
|
|
465
|
-
$hash: cred.passwordHash,
|
|
466
|
-
$enabled: cred.loginEnabled ? 1 : 0,
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
db.query(`DELETE FROM ${AUTH_TABLES.operatorSsoLinks} WHERE operator_id = $id`).run({
|
|
470
|
-
$id: operatorId,
|
|
471
|
-
});
|
|
472
|
-
const insertSso = db.query(
|
|
473
|
-
`INSERT INTO ${AUTH_TABLES.operatorSsoLinks} (operator_id, provider, subject)
|
|
474
|
-
VALUES ($id, $provider, $subject)`,
|
|
516
|
+
VALUES (?, ?, ?)
|
|
517
|
+
ON CONFLICT (operator_id) DO UPDATE SET
|
|
518
|
+
password_hash = EXCLUDED.password_hash,
|
|
519
|
+
login_enabled = EXCLUDED.login_enabled`,
|
|
520
|
+
[cred.operatorId, cred.passwordHash, cred.loginEnabled],
|
|
475
521
|
);
|
|
522
|
+
|
|
523
|
+
await sql.exec(`DELETE FROM ${sso} WHERE operator_id = ?`, [operatorId]);
|
|
476
524
|
for (const link of store.ssoLinks.get(operatorId) ?? []) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
525
|
+
await sql.exec(`INSERT INTO ${sso} (operator_id, provider, subject) VALUES (?, ?, ?)`, [
|
|
526
|
+
link.operatorId,
|
|
527
|
+
link.provider,
|
|
528
|
+
link.subject,
|
|
529
|
+
]);
|
|
482
530
|
}
|
|
483
531
|
|
|
484
|
-
|
|
485
|
-
$id: operatorId,
|
|
486
|
-
});
|
|
487
|
-
const insertRole = db.query(
|
|
488
|
-
`INSERT INTO ${AUTH_TABLES.operatorRoles} (operator_id, role)
|
|
489
|
-
VALUES ($id, $role)`,
|
|
490
|
-
);
|
|
532
|
+
await sql.exec(`DELETE FROM ${roles} WHERE operator_id = ?`, [operatorId]);
|
|
491
533
|
for (const role of store.roles.get(operatorId) ?? []) {
|
|
492
|
-
|
|
534
|
+
await sql.exec(`INSERT INTO ${roles} (operator_id, role) VALUES (?, ?)`, [operatorId, role]);
|
|
493
535
|
}
|
|
494
536
|
}
|