@kedem/okdb 1.0.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/LICENSE +57 -0
- package/README.md +344 -0
- package/banner.jpg +0 -0
- package/bin/okdb.js +2 -0
- package/docs/OKDB-ARCHITECTURE.md +1036 -0
- package/docs/auth-and-sync.md +558 -0
- package/docs/auth-permissions.md +143 -0
- package/docs/change-log.md +169 -0
- package/docs/data-model.md +174 -0
- package/docs/embeddings.md +338 -0
- package/docs/files.md +131 -0
- package/docs/fts.md +139 -0
- package/docs/functions.md +514 -0
- package/docs/getting-started.md +233 -0
- package/docs/http-api.md +705 -0
- package/docs/index.md +115 -0
- package/docs/indexes.md +207 -0
- package/docs/logging.md +116 -0
- package/docs/manifest.json +38 -0
- package/docs/migration.md +154 -0
- package/docs/pipelines.md +598 -0
- package/docs/plugins.md +144 -0
- package/docs/querying.md +204 -0
- package/docs/queue.md +637 -0
- package/docs/sync.md +156 -0
- package/docs/transactions.md +140 -0
- package/docs/ttl.md +375 -0
- package/okdb.js +1 -0
- package/package.json +26 -0
- package/public/admin-app.js +1 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/components/admin-route-host.ok.js +1 -0
- package/public/components/env-picker.ok.js +1 -0
- package/public/components/field-path-input.ok.js +1 -0
- package/public/components/field.html +6 -0
- package/public/components/geo-map.ok.js +1 -0
- package/public/components/ok-cron-input.ok.js +1 -0
- package/public/components/ok-dropzone.ok.js +1 -0
- package/public/components/ok-js-input.ok.js +1 -0
- package/public/components/ok-json-input.ok.js +1 -0
- package/public/components/ok-text-monaco-input.ok.js +1 -0
- package/public/components/theme-toggle.ok.js +1 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/logo.png +0 -0
- package/public/images/logo.svg +333 -0
- package/public/index.html +6 -0
- package/public/layouts/_default.ok.html +1 -0
- package/public/layouts/okdb-cube.ok.html +1 -0
- package/public/login-app.js +1 -0
- package/public/login.html +1 -0
- package/public/modules/ok.js +1 -0
- package/public/sections/api/index.ok.html +1 -0
- package/public/sections/api/parts/api-endpoint.ok.js +1 -0
- package/public/sections/auth/index.ok.html +1 -0
- package/public/sections/auth/modals/create-token-modal.ok.js +1 -0
- package/public/sections/auth/modals/edit-token-modal.ok.js +1 -0
- package/public/sections/auth/modals/perm-info-popover.ok.js +1 -0
- package/public/sections/auth/parts/token-detail.ok.js +1 -0
- package/public/sections/auth/parts/token-list.ok.js +1 -0
- package/public/sections/db/index.ok.html +1 -0
- package/public/sections/db/modals/add-item-modal.ok.js +1 -0
- package/public/sections/db/modals/create-env-modal.ok.js +1 -0
- package/public/sections/db/modals/create-fts-modal.ok.js +1 -0
- package/public/sections/db/modals/create-index-modal.ok.js +1 -0
- package/public/sections/db/modals/create-pipeline-modal.ok.js +1 -0
- package/public/sections/db/modals/demo-env-modal.ok.js +1 -0
- package/public/sections/db/modals/remove-env-modal.ok.js +1 -0
- package/public/sections/db/modals/schema-edit-modal.ok.js +1 -0
- package/public/sections/db/parts/db-overview.ok.js +1 -0
- package/public/sections/db/parts/db-pipelines-panel.ok.js +1 -0
- package/public/sections/db/parts/edit-row.ok.js +1 -0
- package/public/sections/db/parts/tm-timeline.ok.js +1 -0
- package/public/sections/db/parts/type-changelog.ok.js +1 -0
- package/public/sections/db/parts/type-list.ok.js +1 -0
- package/public/sections/db/parts/type-pipelines-tab.ok.js +1 -0
- package/public/sections/db/parts/type-schema-tab.ok.js +1 -0
- package/public/sections/db/parts/type-viewer.ok.js +1 -0
- package/public/sections/db/parts/type-violations-tab.ok.js +1 -0
- package/public/sections/docs/index.ok.html +1 -0
- package/public/sections/embeddings/index.ok.html +1 -0
- package/public/sections/embeddings/modals/register-model-modal.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-create-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-embedder-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-engine-declaration-editor.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-engine-list.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-indexer-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-lifecycle-bar.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-model-catalog.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-overview.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-search-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embed-worker-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/embeddings-engine-panel.ok.js +1 -0
- package/public/sections/embeddings/parts/engines-overview.ok.js +1 -0
- package/public/sections/embeddings/parts/pipeline-create-panel.ok.js +1 -0
- package/public/sections/engines/engine-panel-shared-style.js +1 -0
- package/public/sections/engines/engine-ui-utils.js +1 -0
- package/public/sections/engines/index.ok.html +1 -0
- package/public/sections/engines/modals/engine-modal.ok.js +1 -0
- package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -0
- package/public/sections/engines/parts/engine-lifecycle-actions.ok.js +1 -0
- package/public/sections/engines/parts/engine-panel-shell.ok.js +1 -0
- package/public/sections/engines/parts/engine-resource-card.ok.js +1 -0
- package/public/sections/engines/parts/engine-row-item.ok.js +1 -0
- package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -0
- package/public/sections/files/index.ok.html +1 -0
- package/public/sections/files/parts/files-blob-status.ok.js +1 -0
- package/public/sections/files/parts/files-collection-list.ok.js +1 -0
- package/public/sections/files/parts/files-collection-panel.ok.js +1 -0
- package/public/sections/files/parts/files-file-list.ok.js +1 -0
- package/public/sections/files/parts/files-overview.ok.js +1 -0
- package/public/sections/files/parts/files-path-browser.ok.js +1 -0
- package/public/sections/files/parts/files-upload-panel.ok.js +1 -0
- package/public/sections/functions/index.ok.html +1 -0
- package/public/sections/functions/modals/function-modal.ok.js +1 -0
- package/public/sections/functions/parts/function-detail.ok.js +1 -0
- package/public/sections/functions/parts/function-logs.ok.js +1 -0
- package/public/sections/functions/parts/function-runner.ok.js +1 -0
- package/public/sections/functions/parts/functions-list.ok.js +1 -0
- package/public/sections/license/index.ok.html +1 -0
- package/public/sections/license/parts/license-panel.ok.js +1 -0
- package/public/sections/processors/index.ok.html +1 -0
- package/public/sections/queue/index.ok.html +1 -0
- package/public/sections/queue/modals/create-bucket-modal.ok.js +1 -0
- package/public/sections/queue/modals/edit-bucket-modal.ok.js +1 -0
- package/public/sections/queue/modals/edit-job-modal.ok.js +1 -0
- package/public/sections/queue/modals/enqueue-job-modal.ok.js +1 -0
- package/public/sections/queue/modals/job-type-modal.ok.js +1 -0
- package/public/sections/queue/parts/ok-job-form.ok.js +1 -0
- package/public/sections/queue/parts/queue-buckets.ok.js +1 -0
- package/public/sections/queue/parts/queue-job-types.ok.js +1 -0
- package/public/sections/queue/parts/queue-jobs.ok.js +1 -0
- package/public/sections/queue/parts/queue-types.ok.js +1 -0
- package/public/sections/queue/parts/queue-workers.ok.js +1 -0
- package/public/sections/sync/index.ok.html +5 -0
- package/public/sections/sync/parts/sync-node-detail.ok.js +1 -0
- package/public/sections/sync/parts/sync-retention.ok.js +1 -0
- package/public/sections/sync/parts/sync-topology.ok.js +1 -0
- package/public/sections/system/index.ok.html +1 -0
- package/public/sections/system/modals/data-ops-modal.ok.js +1 -0
- package/public/sections/system/parts/system-api-panel.ok.js +1 -0
- package/public/sections/system/parts/system-auth-panel.ok.js +1 -0
- package/public/sections/system/parts/system-log-history.ok.js +1 -0
- package/public/sections/system/parts/system-log.ok.js +1 -0
- package/public/sections/system/parts/system-status.ok.js +1 -0
- package/public/sections/system/parts/system-sync-panel.ok.js +1 -0
- package/public/sections/views/index.ok.html +1 -0
- package/public/sections/views/modals/create-view-modal.ok.js +1 -0
- package/public/sections/views/parts/view-detail.ok.js +1 -0
- package/public/sections/views/parts/views-list.ok.js +1 -0
- package/public/sections/views/parts/views-overview.ok.js +1 -0
- package/public/setup-app.js +1 -0
- package/public/setup.html +1 -0
- package/public/site.webmanifest +1 -0
- package/public/stores/auth-store.js +1 -0
- package/public/stores/events-store.js +1 -0
- package/public/stores/functions-store.js +1 -0
- package/public/stores/okdb-store.js +1 -0
- package/public/stores/theme-store.js +1 -0
- package/public/styles.css +1 -0
- package/public/test-cron-input.html +10 -0
- package/public/test-json-input.html +101 -0
- package/public/util/permissions.js +1 -0
- package/public/util/sift-schema.js +1 -0
- package/public/vendor/okjs/ok.esm.min.js +2 -0
- package/public/vendor/okjs/toolkit/behavior/async.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-chart.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-doc-viewer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-marked.ok.js +1 -0
- package/public/vendor/okjs/toolkit/components/ok-monaco.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-layer-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-layer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-overlay.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/ok-sandbox.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/transition.ok.js +1 -0
- package/public/vendor/okjs/toolkit/core/virtualized.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-console-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-console.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-inspector-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-inspector.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-js-tree.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-timeline-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/devtools/ok-timeline.ok.js +1 -0
- package/public/vendor/okjs/toolkit/docs/doc-viewer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-component.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-example.ok.js +1 -0
- package/public/vendor/okjs/toolkit/editor/ok-live-shell.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-dropdown.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-icon.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-select.ok.js +1 -0
- package/public/vendor/okjs/toolkit/elements/ok-table.ok.js +1 -0
- package/public/vendor/okjs/toolkit/errors/ok-crash-boundary.ok.js +1 -0
- package/public/vendor/okjs/toolkit/errors/ok-crash-info.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-editor-examples.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-key-editor-tips.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-key-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/i18n-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/i18n/ok-i18n.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-contextmenu.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-dismiss-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-draggable.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-flyover-panel.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-flyover.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-highlight-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-highlight.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-hotkeys-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-picker-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-popout-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-popout.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-portal-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/interaction/ok-submenu.ok.js +1 -0
- package/public/vendor/okjs/toolkit/layout/ok-resizer.ok.js +1 -0
- package/public/vendor/okjs/toolkit/manifest.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-modal-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-modal.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-toast-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/modals/ok-window.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/hash-router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/history-router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/navigation/router.ok.js +1 -0
- package/public/vendor/okjs/toolkit/sync/reactive-sync-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/sync/reactive-sync.js +1 -0
- package/public/vendor/okjs/toolkit/theming/blocks/ok-design-button.css.js +1 -0
- package/public/vendor/okjs/toolkit/theming/blocks/ok-design-input.css.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design-style.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design-vars.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-design.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-input.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-service.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-style.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-utils.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-variable.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-vm.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor.ok.js +1 -0
- package/public/vendor/okjs/toolkit/theming/ok-theme.ok.js +1 -0
- package/public/view-models/type-query-vm.js +1 -0
- package/types/environment.d.ts +130 -0
- package/types/errors.d.ts +98 -0
- package/types/features/admin.d.ts +4 -0
- package/types/features/api.d.ts +18 -0
- package/types/features/auth.d.ts +31 -0
- package/types/features/embeddings.d.ts +39 -0
- package/types/features/engines.d.ts +23 -0
- package/types/features/files.d.ts +29 -0
- package/types/features/fts.d.ts +25 -0
- package/types/features/functions.d.ts +23 -0
- package/types/features/mcp.d.ts +17 -0
- package/types/features/pipelines.d.ts +19 -0
- package/types/features/queue.d.ts +189 -0
- package/types/features/sync.d.ts +34 -0
- package/types/features/time-machine.d.ts +32 -0
- package/types/features/views.d.ts +16 -0
- package/types/index.d.ts +153 -0
- package/types/options.d.ts +202 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# Auth and Sync
|
|
2
|
+
|
|
3
|
+
This document covers how authentication works, how tokens are issued and verified, how OAuth fits in, and how node-to-node sync is secured — including the current gap in sync auth and what to do about it.
|
|
4
|
+
|
|
5
|
+
For the full list of permissions, operations, and token recipes see **[Auth Permissions](./auth-permissions.md)**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Part 1 — Authentication
|
|
10
|
+
|
|
11
|
+
### The core invariant
|
|
12
|
+
|
|
13
|
+
Every authenticated request goes through a single path:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
HTTP request
|
|
17
|
+
↓
|
|
18
|
+
db.auth.authenticateRequest(req)
|
|
19
|
+
↓
|
|
20
|
+
token verification
|
|
21
|
+
↓
|
|
22
|
+
auth result { type, roles, isAuthenticated }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
There is no user database. Authentication is entirely config-driven.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### Auth modes
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
auth.mode = "open" | "secure"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Mode | Behaviour |
|
|
36
|
+
| -------- | ---------------------------------------------------------------------------------------- |
|
|
37
|
+
| `open` | All requests pass. A system user with `admin` role is injected. No credentials required. |
|
|
38
|
+
| `secure` | All `/api/*` and `/admin/*` routes require a valid token or credential. |
|
|
39
|
+
|
|
40
|
+
**Legacy alias:** `mode: "users"` is treated as `"secure"` for backward compatibility.
|
|
41
|
+
|
|
42
|
+
**Default behaviour** — mode is inferred automatically if not set:
|
|
43
|
+
|
|
44
|
+
| Config present | Inferred mode |
|
|
45
|
+
| --------------------------------------------- | ------------- |
|
|
46
|
+
| `auth.admin` or `auth.tokens` or `api.tokens` | `secure` |
|
|
47
|
+
| Nothing | `open` |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Configuration
|
|
52
|
+
|
|
53
|
+
A complete `.kdbconfig`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"port": 8484,
|
|
58
|
+
"path": "./okdb-data",
|
|
59
|
+
"auth": {
|
|
60
|
+
"mode": "secure",
|
|
61
|
+
"admin": "admin:password",
|
|
62
|
+
"tokens": {
|
|
63
|
+
"my-api-key": ["admin"],
|
|
64
|
+
"read-only-key": ["read"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or in code:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const okdb = new OKDB('./data', {
|
|
74
|
+
auth: {
|
|
75
|
+
mode: 'secure',
|
|
76
|
+
admin: 'admin:password',
|
|
77
|
+
tokens: {
|
|
78
|
+
'my-api-key': ['admin'],
|
|
79
|
+
'read-only-key': ['read'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
CLI flags override config:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
okdb --auth open # force open mode
|
|
89
|
+
okdb --admin admin:secret # set admin credential
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Credential types
|
|
95
|
+
|
|
96
|
+
There are exactly three credential types in the system:
|
|
97
|
+
|
|
98
|
+
#### 1. Admin credential (`auth.admin`)
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "auth": { "admin": "admin:password" } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- A single `username:password` string in config.
|
|
105
|
+
- Used **only** by the admin UI login form (`POST /auth/login`).
|
|
106
|
+
- On success, the server issues an **HMAC session token**, sets it in a cookie, and returns it in the response body.
|
|
107
|
+
- Node-local. Not replicated. Not stored in the database.
|
|
108
|
+
|
|
109
|
+
#### 2. Static tokens (`auth.tokens`)
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "auth": { "tokens": { "my-key": ["admin"], "ro-key": ["read"] } } }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Named tokens with explicit role lists, defined in config.
|
|
116
|
+
- Sent as `Authorization: Bearer <token>` on every request.
|
|
117
|
+
- Verified by a **direct map lookup** — no HMAC, no expiry.
|
|
118
|
+
- Node-local. Not replicated.
|
|
119
|
+
- Also accepted as a plain array (backward compat): `["my-key"]` → all get `["admin"]` role.
|
|
120
|
+
|
|
121
|
+
Legacy `api.tokens` is automatically merged into static tokens:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "api": { "tokens": ["my-token"] } }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 3. Issued HMAC tokens
|
|
128
|
+
|
|
129
|
+
These are short-lived tokens created by the server after a successful login or OAuth exchange.
|
|
130
|
+
|
|
131
|
+
**Structure** (not a JWT):
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
base64url(payload).hmac_sha256(payload, tokenSecret)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Payload:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"iat": 1710400000000,
|
|
142
|
+
"exp": 1710403600000,
|
|
143
|
+
"sub": "admin",
|
|
144
|
+
"username": "admin",
|
|
145
|
+
"roles": ["admin"]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- Default TTL: **1 hour**
|
|
150
|
+
- Clock-skew tolerance: **30 seconds**
|
|
151
|
+
- `tokenSecret` is auto-generated per node and stored in the local LMDB as `__tokenSecret` (never replicated)
|
|
152
|
+
- Sent as `Authorization: Bearer <token>` or via the `okdb_session` cookie
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Token verification order
|
|
157
|
+
|
|
158
|
+
For every incoming request with a `Bearer` token:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
1. Check static token map → immediate if found
|
|
162
|
+
2. Verify HMAC issued token → if signature valid and not expired
|
|
163
|
+
3. Validate OIDC access token → if oauth.issuer is configured
|
|
164
|
+
4. Reject → 401
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Cookie path (admin UI sessions):
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
1. Verify HMAC issued token from cookie
|
|
171
|
+
2. Reject → 401
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### Login flow (admin UI)
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
POST /auth/login
|
|
180
|
+
{ "username": "admin", "password": "password" }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Server:
|
|
184
|
+
|
|
185
|
+
1. Compares against `auth.admin` credential (case-insensitive username)
|
|
186
|
+
2. If match → issues HMAC token
|
|
187
|
+
3. Returns token in body + sets `okdb_session` cookie
|
|
188
|
+
4. Returns `{ access_token, expires_in }`
|
|
189
|
+
|
|
190
|
+
The issued token can then be used as a Bearer token for API calls.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### OAuth flow
|
|
195
|
+
|
|
196
|
+
OAuth is used **only to obtain a token**. It does not create users, does not store identities, and does not require a user database.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
GET /admin/auth/start?redirect=/admin/index.html
|
|
200
|
+
→ redirect to OAuth provider
|
|
201
|
+
|
|
202
|
+
GET /admin/auth/callback?code=...&state=...
|
|
203
|
+
→ exchange code with provider
|
|
204
|
+
→ receive email + subject from provider
|
|
205
|
+
→ check email against allowedEmails / allowedDomains
|
|
206
|
+
→ if allowed: issue HMAC token, set okdb_session cookie, redirect
|
|
207
|
+
→ if not allowed: 403
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The token payload for OAuth logins uses the email as `username` and roles from `defaultRoles` config (defaults to `["admin"]`).
|
|
211
|
+
|
|
212
|
+
**Nothing is persisted.** The OKDB node itself is not an identity provider. OAuth is just a way to authenticate to obtain a node-local session token.
|
|
213
|
+
|
|
214
|
+
#### Email allowlisting (required)
|
|
215
|
+
|
|
216
|
+
You **must** configure `allowedEmails` or `allowedDomains` (or both) to specify which accounts are accepted. If neither is set, **all OAuth logins are rejected** (secure by default).
|
|
217
|
+
|
|
218
|
+
| Field | Type | Description |
|
|
219
|
+
| ---------------- | ---------- | ------------------------------------------------------------ |
|
|
220
|
+
| `allowedEmails` | `string[]` | Exact email addresses, e.g. `["alice@corp.com"]` |
|
|
221
|
+
| `allowedDomains` | `string[]` | Domain suffixes, e.g. `["corp.com"]` matches `*@corp.com` |
|
|
222
|
+
| `defaultRoles` | `string[]` | Roles assigned to allowed OAuth users (default: `["admin"]`) |
|
|
223
|
+
|
|
224
|
+
If an email matches **either** list, it is allowed.
|
|
225
|
+
|
|
226
|
+
Configure OAuth via the `auth.providers` block:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
const okdb = new OKDB('./data', {
|
|
230
|
+
auth: {
|
|
231
|
+
mode: 'secure',
|
|
232
|
+
providers: {
|
|
233
|
+
google: {
|
|
234
|
+
issuer: 'https://accounts.google.com',
|
|
235
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
236
|
+
clientSecret: 'YOUR_CLIENT_SECRET',
|
|
237
|
+
redirectUri: 'http://localhost:8484/admin/auth/callback',
|
|
238
|
+
scopes: ['openid', 'email', 'profile'],
|
|
239
|
+
|
|
240
|
+
// Who is allowed to authenticate (required — no allowlist = all rejected)
|
|
241
|
+
allowedDomains: ['mycompany.com'], // anyone @mycompany.com
|
|
242
|
+
allowedEmails: ['contractor@gmail.com'], // specific external accounts
|
|
243
|
+
defaultRoles: ['admin'], // roles for allowed users
|
|
244
|
+
|
|
245
|
+
getAuthorizationUrl: ({ state }) => `https://accounts.google.com/o/oauth2/v2/auth?...&state=${state}`,
|
|
246
|
+
exchangeCode: async ({ code }) => {
|
|
247
|
+
// exchange code for tokens, return { subject, email, claims }
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The same allowlist applies to direct OIDC bearer tokens (API/MCP clients sending a Google access token as `Authorization: Bearer <jwt>`).
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Route protection summary
|
|
260
|
+
|
|
261
|
+
| Route | Requirement |
|
|
262
|
+
| ------------------- | -------------------------------------------------------- |
|
|
263
|
+
| `GET /api/*` | Any valid token (static, issued, or OIDC), or open mode |
|
|
264
|
+
| `POST /auth/login` | No auth (public endpoint) |
|
|
265
|
+
| `POST /auth/logout` | No auth (public endpoint) |
|
|
266
|
+
| `GET /admin/login` | No auth (public endpoint) |
|
|
267
|
+
| `GET /admin/*` | Valid token in cookie or Bearer header with `admin` role |
|
|
268
|
+
| `/api/sync/*` | Valid token (same as API) — see note in Part 2 |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `GET /admin/session`
|
|
273
|
+
|
|
274
|
+
Returns the current session state:
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"authenticated": true,
|
|
279
|
+
"user": {
|
|
280
|
+
"id": "admin",
|
|
281
|
+
"username": "admin",
|
|
282
|
+
"roles": ["admin"]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Returns `401` when not authenticated.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### Logout
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
POST /auth/logout
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Clears the `okdb_session` cookie (`Max-Age=0`). No server-side state to invalidate — tokens are stateless.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Part 2 — Sync
|
|
302
|
+
|
|
303
|
+
### How sync works
|
|
304
|
+
|
|
305
|
+
OKDB uses a **pull-based, last-write-wins (LWW)** replication model over HTTP. There is no central coordinator, no consensus protocol.
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
1. A write increments the local HLC clock and logs a change entry
|
|
309
|
+
2. On clock advance, a UDP multicast poke is sent on the LAN
|
|
310
|
+
3. Peers that receive the poke pull a delta from the changed node
|
|
311
|
+
4. Each change is compared by HLC timestamp — the higher wins
|
|
312
|
+
5. Ties are broken by origin node ID (lexicographic)
|
|
313
|
+
6. Applied changes use origin= to prevent echo-loops
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Sync is **eventual consistency** designed for loosely-coupled topologies: multi-instance servers, edge clusters, desktop nodes.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
### Node identity
|
|
321
|
+
|
|
322
|
+
Each node has a persistent `nodeId` (UUID) stored as `__identity` in the local LMDB. This is auto-generated on first open and never changes.
|
|
323
|
+
|
|
324
|
+
Nodes register themselves and discover peers via the `~sync_nodes` type in the `~system` environment, which itself replicates — so adding a peer on one node eventually propagates to all peers, forming a mesh.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Setting up sync
|
|
329
|
+
|
|
330
|
+
```js
|
|
331
|
+
const okdb = new OKDB('./node1', {
|
|
332
|
+
auth: {
|
|
333
|
+
mode: 'secure',
|
|
334
|
+
tokens: { 'cluster-secret': ['admin'] },
|
|
335
|
+
},
|
|
336
|
+
sync: {
|
|
337
|
+
token: 'cluster-secret', // token this node sends when calling peers
|
|
338
|
+
address: 'http://192.168.1.10:8484', // this node's public URL
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
await okdb.open();
|
|
342
|
+
okdb.http.listen(8484);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Or in `.kdbconfig`:
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{
|
|
349
|
+
"auth": {
|
|
350
|
+
"mode": "secure",
|
|
351
|
+
"tokens": {
|
|
352
|
+
"cluster-secret": ["admin"]
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"sync": {
|
|
356
|
+
"token": "cluster-secret",
|
|
357
|
+
"address": "http://192.168.1.10:8484"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### How sync auth works (current model)
|
|
365
|
+
|
|
366
|
+
#### Outbound requests (this node → peer)
|
|
367
|
+
|
|
368
|
+
When this node calls a peer to fetch a delta or register a join, it sends:
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
Authorization: Bearer <sync.token>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The value comes from `sync.token` in config. This is a **static Bearer token** sent on every outbound sync HTTP call.
|
|
375
|
+
|
|
376
|
+
#### Inbound requests (peer → this node)
|
|
377
|
+
|
|
378
|
+
The peer's `sync.token` must match one of the **static tokens** configured on the receiving node (`auth.tokens`).
|
|
379
|
+
|
|
380
|
+
This means the setup is:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
node A has: auth.tokens: { "cluster-secret": ["admin"] }
|
|
384
|
+
node B has: sync.token: "cluster-secret"
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Node B calls node A's `/api/sync/delta` → sends `Authorization: Bearer cluster-secret` → node A looks up `cluster-secret` in its static token map → grants access.
|
|
388
|
+
|
|
389
|
+
#### The shared-secret model
|
|
390
|
+
|
|
391
|
+
In practice, all nodes in a cluster share the **same token**:
|
|
392
|
+
|
|
393
|
+
- Every node puts the shared token in `sync.token` (so it sends it outbound)
|
|
394
|
+
- Every node puts the shared token in `auth.tokens` (so it accepts it inbound)
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"auth": {
|
|
399
|
+
"tokens": { "cluster-secret": ["admin"] }
|
|
400
|
+
},
|
|
401
|
+
"sync": {
|
|
402
|
+
"token": "cluster-secret"
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
This means a single secret grants admin access to all nodes. It's simple and works, but has the following tradeoff: **anyone who knows the sync token can make admin API calls to any node in the cluster.**
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### Connecting nodes
|
|
412
|
+
|
|
413
|
+
#### Via code
|
|
414
|
+
|
|
415
|
+
```js
|
|
416
|
+
// Node 2 joins Node 1
|
|
417
|
+
await node2.sync.join('http://192.168.1.10:8484');
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
This:
|
|
421
|
+
|
|
422
|
+
1. Fetches `GET /api/sync/info` from Node 1 to get its node ID
|
|
423
|
+
2. Stores Node 1's record in `~sync_nodes` (replicates to mesh)
|
|
424
|
+
3. Creates a bidirectional data link
|
|
425
|
+
4. POSTs to Node 1's `/api/sync/join` so Node 1 registers Node 2
|
|
426
|
+
5. Pulls an initial delta immediately
|
|
427
|
+
|
|
428
|
+
#### Via admin UI
|
|
429
|
+
|
|
430
|
+
Go to **System → Sync → Connect**. Enter the target node's URL and click Connect.
|
|
431
|
+
|
|
432
|
+
The UI calls `POST /api/sync/connect`:
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
{ "address": "http://192.168.1.10:8484" }
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
The joining node's `sync.token` is sent as the `Authorization` header. The target node must have that token in its `auth.tokens`.
|
|
439
|
+
|
|
440
|
+
#### Via API
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
POST /api/sync/connect
|
|
444
|
+
Authorization: Bearer <admin-token>
|
|
445
|
+
{ "address": "http://192.168.1.10:8484" }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### Multi-node cluster example
|
|
451
|
+
|
|
452
|
+
```js
|
|
453
|
+
const nodes = [8484, 8485, 8486].map((port) => {
|
|
454
|
+
const okdb = new OKDB(`./node-${port}`, {
|
|
455
|
+
auth: {
|
|
456
|
+
mode: 'secure',
|
|
457
|
+
tokens: { 'cluster-secret': ['admin'] },
|
|
458
|
+
},
|
|
459
|
+
sync: {
|
|
460
|
+
token: 'cluster-secret',
|
|
461
|
+
address: `http://localhost:${port}`,
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
okdb._port = port;
|
|
465
|
+
return okdb;
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
await Promise.all(nodes.map((n) => n.open()));
|
|
469
|
+
for (const node of nodes) node.http.listen(node._port);
|
|
470
|
+
|
|
471
|
+
// Connect node 1 and 2 to node 0 (mesh forms automatically from ~sync_nodes sync)
|
|
472
|
+
await nodes[1].sync.join('http://localhost:8484');
|
|
473
|
+
await nodes[2].sync.join('http://localhost:8484');
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### What syncs and what doesn't
|
|
479
|
+
|
|
480
|
+
| Data | Syncs | Notes |
|
|
481
|
+
| -------------------------------- | ----- | ------------------------------------- |
|
|
482
|
+
| Application data (default env) | ✅ | All `put`/`delete` operations |
|
|
483
|
+
| Custom `sync: true` environments | ✅ | Created via `createEnvironment` |
|
|
484
|
+
| `~system` environment | ✅ | Including `~sync_nodes` peer registry |
|
|
485
|
+
| `__identity` (node UUID) | ❌ | Raw LMDB key, never in changelog |
|
|
486
|
+
| `__tokenSecret` | ❌ | Raw LMDB key, never in changelog |
|
|
487
|
+
| `auth.admin` credential | ❌ | Config only, never in database |
|
|
488
|
+
| `auth.tokens` | ❌ | Config only, never in database |
|
|
489
|
+
| `sync.token` | ❌ | Config only, never in database |
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
### The sync auth gap
|
|
494
|
+
|
|
495
|
+
**Current state:** Sync uses a shared static token. This works but has one weakness — the sync token is also a full admin credential on every node it's registered on. There is no per-node identity or revocation.
|
|
496
|
+
|
|
497
|
+
**Planned (Phase 2):** Cryptographic node identity using Ed25519 keypairs.
|
|
498
|
+
|
|
499
|
+
- Each node generates a keypair on first open
|
|
500
|
+
- Private key: stored as `__nodePrivateKey` in local LMDB, never replicated
|
|
501
|
+
- Public key: stored in `~sync_nodes` (replicates to peers)
|
|
502
|
+
- Outbound sync requests: signed with private key (`X-OKDB-NodeId`, `X-OKDB-Signature`)
|
|
503
|
+
- Inbound sync requests: verified against stored public key
|
|
504
|
+
- Trust: local-only (`__sync:trusted_peers`), non-transitive
|
|
505
|
+
- Discovering a peer's identity (via `~sync_nodes` replication) does **not** grant trust
|
|
506
|
+
|
|
507
|
+
Until Phase 2 is implemented, the shared-secret model is the operational approach.
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
### Sync endpoints reference
|
|
512
|
+
|
|
513
|
+
| Method | Path | Auth required | Description |
|
|
514
|
+
| ------- | ------------------- | ------------- | -------------------------------- |
|
|
515
|
+
| `GET` | `/api/sync/info` | Yes | Node ID, clock, available envs |
|
|
516
|
+
| `POST` | `/api/sync/delta` | Yes | Pull changes since `from_clocks` |
|
|
517
|
+
| `POST` | `/api/sync/join` | Yes | Accept incoming peer join |
|
|
518
|
+
| `POST` | `/api/sync/connect` | Yes | Initiate join to remote node |
|
|
519
|
+
| `GET` | `/api/sync/peers` | Yes | List known peers and status |
|
|
520
|
+
| `PATCH` | `/api/sync/self` | Yes | Update this node's address/meta |
|
|
521
|
+
|
|
522
|
+
All sync endpoints require a valid token with `admin` role (same as the rest of the API).
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
### Sync config reference
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"sync": {
|
|
531
|
+
"token": "shared-secret",
|
|
532
|
+
"address": "http://this-node:8484",
|
|
533
|
+
"delta_limit": 500,
|
|
534
|
+
"auto_reconcile": true
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
| Key | Default | Description |
|
|
540
|
+
| ---------------- | ------- | --------------------------------------------- |
|
|
541
|
+
| `token` | — | Bearer token sent on outbound sync calls |
|
|
542
|
+
| `address` | — | This node's public URL, advertised to peers |
|
|
543
|
+
| `delta_limit` | `500` | Max changes per delta pull |
|
|
544
|
+
| `auto_reconcile` | `true` | Automatically pull from peers on clock change |
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Summary
|
|
549
|
+
|
|
550
|
+
| Concept | Mechanism |
|
|
551
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
552
|
+
| Admin UI login | `auth.admin` credential → HMAC token → cookie |
|
|
553
|
+
| API access | Static token (`auth.tokens`) or issued HMAC token |
|
|
554
|
+
| OAuth | Exchange code with provider → issue HMAC token → no persistence |
|
|
555
|
+
| Sync auth (current) | `sync.token` = shared static Bearer token |
|
|
556
|
+
| Sync auth (Phase 2) | Ed25519 per-node keypairs, local trust records |
|
|
557
|
+
| Token secret | Auto-generated, stored locally as `__tokenSecret`, never replicated |
|
|
558
|
+
| Node identity | Auto-generated UUID stored locally as `__identity`, never replicated |
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Auth Permissions
|
|
2
|
+
|
|
3
|
+
This page documents the permission model used by okdb tokens.
|
|
4
|
+
|
|
5
|
+
## What a permission is
|
|
6
|
+
|
|
7
|
+
A permission is a string in the form `namespace:operation` (e.g. `data:read`, `queue:work`).
|
|
8
|
+
Each API route declares the single permission required to call it.
|
|
9
|
+
|
|
10
|
+
Tokens carry two kinds of grants:
|
|
11
|
+
|
|
12
|
+
- **Global permissions** (`permissions` array) — apply to every request regardless of which environment it targets.
|
|
13
|
+
- **Per-env grants** (`grants` object) — extra permissions that apply only when a request targets a specific named environment.
|
|
14
|
+
|
|
15
|
+
The effective grant set for a given request is the union of both.
|
|
16
|
+
|
|
17
|
+
## The permission model
|
|
18
|
+
|
|
19
|
+
24 permissions across 10 user-facing namespaces and 1 gate.
|
|
20
|
+
|
|
21
|
+
### Environment-scoped namespaces
|
|
22
|
+
|
|
23
|
+
These can be granted globally or as per-env grants.
|
|
24
|
+
|
|
25
|
+
| Namespace | Operations | What it covers |
|
|
26
|
+
| ----------- | ----------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `data` | `read`, `write` | Records: get/list/byIndex, create/update/delete, bulk import |
|
|
28
|
+
| `schema` | `read`, `write` | Type definitions, all index registrations (byIndex, FTS, vector), TTL policy |
|
|
29
|
+
| `search` | `query` | Run FTS / vector searches |
|
|
30
|
+
| `queue` | `read`, `write`, `work` | `read`=list/stats; `write`=enqueue/cancel/retry/clear/buckets; `work`=worker-side claim/heartbeat/complete/fail |
|
|
31
|
+
| `files` | `read`, `write` | Blob storage: upload, download, list, delete, metadata |
|
|
32
|
+
| `functions` | `read`, `write`, `run` | `read`=list/inspect; `write`=register/update/delete; `run`=execute |
|
|
33
|
+
| `env` | `read`, `write` | Env lifecycle: list, info, create, delete, compact |
|
|
34
|
+
|
|
35
|
+
### System-scoped namespaces
|
|
36
|
+
|
|
37
|
+
These are global only (per-env grants cannot hold them).
|
|
38
|
+
|
|
39
|
+
| Namespace | Operations | What it covers |
|
|
40
|
+
| --------- | ----------------------- | ----------------------------------------------------------------------- |
|
|
41
|
+
| `auth` | `read`, `write` | API tokens and users |
|
|
42
|
+
| `system` | `read`, `write` | Server info, logs, events, processor status, backups, engines |
|
|
43
|
+
| `sync` | `read`, `write`, `peer` | Cluster replication — `peer` is for machine-to-machine only (see below) |
|
|
44
|
+
|
|
45
|
+
### Gate
|
|
46
|
+
|
|
47
|
+
| Namespace | Operations | What it covers |
|
|
48
|
+
| ----------- | --------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `protected` | `read`, `write` | Required to access system types (names starting with `~`), combined with the underlying data/schema permission |
|
|
50
|
+
|
|
51
|
+
## Implied permissions
|
|
52
|
+
|
|
53
|
+
Three rules. Everything else is explicit.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
data:read → schema:read (can't read records without seeing their schema)
|
|
57
|
+
data:write → schema:read (can't write records without knowing their type)
|
|
58
|
+
search:query → schema:read (searches return records; caller needs the type definition)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Implication is one-directional. `schema:read` does not imply `data:read`.
|
|
62
|
+
|
|
63
|
+
## Wildcards
|
|
64
|
+
|
|
65
|
+
| Grant | Matches |
|
|
66
|
+
| ---------------------- | -------------------------------------- |
|
|
67
|
+
| `*` | Everything |
|
|
68
|
+
| `ns:*` (e.g. `data:*`) | All operations in the namespace |
|
|
69
|
+
| `*:op` (e.g. `*:read`) | The given operation in every namespace |
|
|
70
|
+
|
|
71
|
+
## Per-env grants
|
|
72
|
+
|
|
73
|
+
Shape of a token with per-env grants:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"permissions": ["auth:read"],
|
|
78
|
+
"grants": {
|
|
79
|
+
"production": ["data:read", "schema:read"],
|
|
80
|
+
"staging": ["data:*"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The token can list tokens globally, read records in `production`, and do anything data-related in `staging`.
|
|
86
|
+
|
|
87
|
+
## The `protected` gate
|
|
88
|
+
|
|
89
|
+
System types (names prefixed `~`) store internal okdb state. To access them the caller needs **both**:
|
|
90
|
+
|
|
91
|
+
1. The underlying namespace permission (`data:read` to read, `data:write` to write, `queue:work` to claim, etc.)
|
|
92
|
+
2. The matching `protected:read` or `protected:write` gate.
|
|
93
|
+
|
|
94
|
+
Write-side operations (`write`, `work`, `run`, `peer`) require `protected:write`. Read-side operations (`read`, `query`) require `protected:read`.
|
|
95
|
+
|
|
96
|
+
## `sync:peer` — machine permission
|
|
97
|
+
|
|
98
|
+
`sync:peer` gates the cluster replication endpoints. A node holding this token can receive replicated data for any namespace. **Do not grant it to human users.**
|
|
99
|
+
|
|
100
|
+
## Cookbook: common token recipes
|
|
101
|
+
|
|
102
|
+
### Read-only browser client
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{ "permissions": ["data:read"] }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`schema:read` is implied — no need to add it.
|
|
109
|
+
|
|
110
|
+
### App backend (full CRUD + search + queue admin)
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{ "permissions": ["data:read", "data:write", "search:query", "queue:read", "queue:write"] }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Queue worker
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{ "permissions": ["queue:work", "data:write"] }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`queue:work` to claim and complete jobs; `data:write` (or whatever the job needs) to do the work.
|
|
123
|
+
|
|
124
|
+
### Per-env scoped read-only token
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"permissions": [],
|
|
129
|
+
"grants": { "production": ["data:read"] }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Cluster peer node
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{ "permissions": ["sync:peer"] }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Adding a new permission
|
|
140
|
+
|
|
141
|
+
1. Add the namespace/operation to `src/features/auth/okdb-auth-namespaces.js` in `NAMESPACES`.
|
|
142
|
+
2. Add `permission: 'ns:op'` to the route's `access` object.
|
|
143
|
+
3. The catalog endpoint at `GET /admin/api/auth/permissions` picks it up automatically — no UI changes needed.
|