@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.
Files changed (260) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +344 -0
  3. package/banner.jpg +0 -0
  4. package/bin/okdb.js +2 -0
  5. package/docs/OKDB-ARCHITECTURE.md +1036 -0
  6. package/docs/auth-and-sync.md +558 -0
  7. package/docs/auth-permissions.md +143 -0
  8. package/docs/change-log.md +169 -0
  9. package/docs/data-model.md +174 -0
  10. package/docs/embeddings.md +338 -0
  11. package/docs/files.md +131 -0
  12. package/docs/fts.md +139 -0
  13. package/docs/functions.md +514 -0
  14. package/docs/getting-started.md +233 -0
  15. package/docs/http-api.md +705 -0
  16. package/docs/index.md +115 -0
  17. package/docs/indexes.md +207 -0
  18. package/docs/logging.md +116 -0
  19. package/docs/manifest.json +38 -0
  20. package/docs/migration.md +154 -0
  21. package/docs/pipelines.md +598 -0
  22. package/docs/plugins.md +144 -0
  23. package/docs/querying.md +204 -0
  24. package/docs/queue.md +637 -0
  25. package/docs/sync.md +156 -0
  26. package/docs/transactions.md +140 -0
  27. package/docs/ttl.md +375 -0
  28. package/okdb.js +1 -0
  29. package/package.json +26 -0
  30. package/public/admin-app.js +1 -0
  31. package/public/android-chrome-192x192.png +0 -0
  32. package/public/android-chrome-512x512.png +0 -0
  33. package/public/apple-touch-icon.png +0 -0
  34. package/public/components/admin-route-host.ok.js +1 -0
  35. package/public/components/env-picker.ok.js +1 -0
  36. package/public/components/field-path-input.ok.js +1 -0
  37. package/public/components/field.html +6 -0
  38. package/public/components/geo-map.ok.js +1 -0
  39. package/public/components/ok-cron-input.ok.js +1 -0
  40. package/public/components/ok-dropzone.ok.js +1 -0
  41. package/public/components/ok-js-input.ok.js +1 -0
  42. package/public/components/ok-json-input.ok.js +1 -0
  43. package/public/components/ok-text-monaco-input.ok.js +1 -0
  44. package/public/components/theme-toggle.ok.js +1 -0
  45. package/public/favicon-16x16.png +0 -0
  46. package/public/favicon-32x32.png +0 -0
  47. package/public/favicon.ico +0 -0
  48. package/public/images/logo.png +0 -0
  49. package/public/images/logo.svg +333 -0
  50. package/public/index.html +6 -0
  51. package/public/layouts/_default.ok.html +1 -0
  52. package/public/layouts/okdb-cube.ok.html +1 -0
  53. package/public/login-app.js +1 -0
  54. package/public/login.html +1 -0
  55. package/public/modules/ok.js +1 -0
  56. package/public/sections/api/index.ok.html +1 -0
  57. package/public/sections/api/parts/api-endpoint.ok.js +1 -0
  58. package/public/sections/auth/index.ok.html +1 -0
  59. package/public/sections/auth/modals/create-token-modal.ok.js +1 -0
  60. package/public/sections/auth/modals/edit-token-modal.ok.js +1 -0
  61. package/public/sections/auth/modals/perm-info-popover.ok.js +1 -0
  62. package/public/sections/auth/parts/token-detail.ok.js +1 -0
  63. package/public/sections/auth/parts/token-list.ok.js +1 -0
  64. package/public/sections/db/index.ok.html +1 -0
  65. package/public/sections/db/modals/add-item-modal.ok.js +1 -0
  66. package/public/sections/db/modals/create-env-modal.ok.js +1 -0
  67. package/public/sections/db/modals/create-fts-modal.ok.js +1 -0
  68. package/public/sections/db/modals/create-index-modal.ok.js +1 -0
  69. package/public/sections/db/modals/create-pipeline-modal.ok.js +1 -0
  70. package/public/sections/db/modals/demo-env-modal.ok.js +1 -0
  71. package/public/sections/db/modals/remove-env-modal.ok.js +1 -0
  72. package/public/sections/db/modals/schema-edit-modal.ok.js +1 -0
  73. package/public/sections/db/parts/db-overview.ok.js +1 -0
  74. package/public/sections/db/parts/db-pipelines-panel.ok.js +1 -0
  75. package/public/sections/db/parts/edit-row.ok.js +1 -0
  76. package/public/sections/db/parts/tm-timeline.ok.js +1 -0
  77. package/public/sections/db/parts/type-changelog.ok.js +1 -0
  78. package/public/sections/db/parts/type-list.ok.js +1 -0
  79. package/public/sections/db/parts/type-pipelines-tab.ok.js +1 -0
  80. package/public/sections/db/parts/type-schema-tab.ok.js +1 -0
  81. package/public/sections/db/parts/type-viewer.ok.js +1 -0
  82. package/public/sections/db/parts/type-violations-tab.ok.js +1 -0
  83. package/public/sections/docs/index.ok.html +1 -0
  84. package/public/sections/embeddings/index.ok.html +1 -0
  85. package/public/sections/embeddings/modals/register-model-modal.ok.js +1 -0
  86. package/public/sections/embeddings/parts/embed-create-panel.ok.js +1 -0
  87. package/public/sections/embeddings/parts/embed-embedder-panel.ok.js +1 -0
  88. package/public/sections/embeddings/parts/embed-engine-declaration-editor.ok.js +1 -0
  89. package/public/sections/embeddings/parts/embed-engine-list.ok.js +1 -0
  90. package/public/sections/embeddings/parts/embed-indexer-panel.ok.js +1 -0
  91. package/public/sections/embeddings/parts/embed-lifecycle-bar.ok.js +1 -0
  92. package/public/sections/embeddings/parts/embed-model-catalog.ok.js +1 -0
  93. package/public/sections/embeddings/parts/embed-overview.ok.js +1 -0
  94. package/public/sections/embeddings/parts/embed-search-panel.ok.js +1 -0
  95. package/public/sections/embeddings/parts/embed-worker-panel.ok.js +1 -0
  96. package/public/sections/embeddings/parts/embeddings-engine-panel.ok.js +1 -0
  97. package/public/sections/embeddings/parts/engines-overview.ok.js +1 -0
  98. package/public/sections/embeddings/parts/pipeline-create-panel.ok.js +1 -0
  99. package/public/sections/engines/engine-panel-shared-style.js +1 -0
  100. package/public/sections/engines/engine-ui-utils.js +1 -0
  101. package/public/sections/engines/index.ok.html +1 -0
  102. package/public/sections/engines/modals/engine-modal.ok.js +1 -0
  103. package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -0
  104. package/public/sections/engines/parts/engine-lifecycle-actions.ok.js +1 -0
  105. package/public/sections/engines/parts/engine-panel-shell.ok.js +1 -0
  106. package/public/sections/engines/parts/engine-resource-card.ok.js +1 -0
  107. package/public/sections/engines/parts/engine-row-item.ok.js +1 -0
  108. package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -0
  109. package/public/sections/files/index.ok.html +1 -0
  110. package/public/sections/files/parts/files-blob-status.ok.js +1 -0
  111. package/public/sections/files/parts/files-collection-list.ok.js +1 -0
  112. package/public/sections/files/parts/files-collection-panel.ok.js +1 -0
  113. package/public/sections/files/parts/files-file-list.ok.js +1 -0
  114. package/public/sections/files/parts/files-overview.ok.js +1 -0
  115. package/public/sections/files/parts/files-path-browser.ok.js +1 -0
  116. package/public/sections/files/parts/files-upload-panel.ok.js +1 -0
  117. package/public/sections/functions/index.ok.html +1 -0
  118. package/public/sections/functions/modals/function-modal.ok.js +1 -0
  119. package/public/sections/functions/parts/function-detail.ok.js +1 -0
  120. package/public/sections/functions/parts/function-logs.ok.js +1 -0
  121. package/public/sections/functions/parts/function-runner.ok.js +1 -0
  122. package/public/sections/functions/parts/functions-list.ok.js +1 -0
  123. package/public/sections/license/index.ok.html +1 -0
  124. package/public/sections/license/parts/license-panel.ok.js +1 -0
  125. package/public/sections/processors/index.ok.html +1 -0
  126. package/public/sections/queue/index.ok.html +1 -0
  127. package/public/sections/queue/modals/create-bucket-modal.ok.js +1 -0
  128. package/public/sections/queue/modals/edit-bucket-modal.ok.js +1 -0
  129. package/public/sections/queue/modals/edit-job-modal.ok.js +1 -0
  130. package/public/sections/queue/modals/enqueue-job-modal.ok.js +1 -0
  131. package/public/sections/queue/modals/job-type-modal.ok.js +1 -0
  132. package/public/sections/queue/parts/ok-job-form.ok.js +1 -0
  133. package/public/sections/queue/parts/queue-buckets.ok.js +1 -0
  134. package/public/sections/queue/parts/queue-job-types.ok.js +1 -0
  135. package/public/sections/queue/parts/queue-jobs.ok.js +1 -0
  136. package/public/sections/queue/parts/queue-types.ok.js +1 -0
  137. package/public/sections/queue/parts/queue-workers.ok.js +1 -0
  138. package/public/sections/sync/index.ok.html +5 -0
  139. package/public/sections/sync/parts/sync-node-detail.ok.js +1 -0
  140. package/public/sections/sync/parts/sync-retention.ok.js +1 -0
  141. package/public/sections/sync/parts/sync-topology.ok.js +1 -0
  142. package/public/sections/system/index.ok.html +1 -0
  143. package/public/sections/system/modals/data-ops-modal.ok.js +1 -0
  144. package/public/sections/system/parts/system-api-panel.ok.js +1 -0
  145. package/public/sections/system/parts/system-auth-panel.ok.js +1 -0
  146. package/public/sections/system/parts/system-log-history.ok.js +1 -0
  147. package/public/sections/system/parts/system-log.ok.js +1 -0
  148. package/public/sections/system/parts/system-status.ok.js +1 -0
  149. package/public/sections/system/parts/system-sync-panel.ok.js +1 -0
  150. package/public/sections/views/index.ok.html +1 -0
  151. package/public/sections/views/modals/create-view-modal.ok.js +1 -0
  152. package/public/sections/views/parts/view-detail.ok.js +1 -0
  153. package/public/sections/views/parts/views-list.ok.js +1 -0
  154. package/public/sections/views/parts/views-overview.ok.js +1 -0
  155. package/public/setup-app.js +1 -0
  156. package/public/setup.html +1 -0
  157. package/public/site.webmanifest +1 -0
  158. package/public/stores/auth-store.js +1 -0
  159. package/public/stores/events-store.js +1 -0
  160. package/public/stores/functions-store.js +1 -0
  161. package/public/stores/okdb-store.js +1 -0
  162. package/public/stores/theme-store.js +1 -0
  163. package/public/styles.css +1 -0
  164. package/public/test-cron-input.html +10 -0
  165. package/public/test-json-input.html +101 -0
  166. package/public/util/permissions.js +1 -0
  167. package/public/util/sift-schema.js +1 -0
  168. package/public/vendor/okjs/ok.esm.min.js +2 -0
  169. package/public/vendor/okjs/toolkit/behavior/async.ok.js +1 -0
  170. package/public/vendor/okjs/toolkit/components/ok-chart.ok.js +1 -0
  171. package/public/vendor/okjs/toolkit/components/ok-doc-viewer.ok.js +1 -0
  172. package/public/vendor/okjs/toolkit/components/ok-marked.ok.js +1 -0
  173. package/public/vendor/okjs/toolkit/components/ok-monaco.ok.js +1 -0
  174. package/public/vendor/okjs/toolkit/core/ok-layer-service.ok.js +1 -0
  175. package/public/vendor/okjs/toolkit/core/ok-layer.ok.js +1 -0
  176. package/public/vendor/okjs/toolkit/core/ok-overlay.ok.js +1 -0
  177. package/public/vendor/okjs/toolkit/core/ok-sandbox.ok.js +1 -0
  178. package/public/vendor/okjs/toolkit/core/transition.ok.js +1 -0
  179. package/public/vendor/okjs/toolkit/core/virtualized.ok.js +1 -0
  180. package/public/vendor/okjs/toolkit/devtools/ok-console-service.ok.js +1 -0
  181. package/public/vendor/okjs/toolkit/devtools/ok-console.ok.js +1 -0
  182. package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor-service.ok.js +1 -0
  183. package/public/vendor/okjs/toolkit/devtools/ok-fps-monitor.ok.js +1 -0
  184. package/public/vendor/okjs/toolkit/devtools/ok-inspector-service.ok.js +1 -0
  185. package/public/vendor/okjs/toolkit/devtools/ok-inspector.ok.js +1 -0
  186. package/public/vendor/okjs/toolkit/devtools/ok-js-tree.ok.js +1 -0
  187. package/public/vendor/okjs/toolkit/devtools/ok-timeline-service.ok.js +1 -0
  188. package/public/vendor/okjs/toolkit/devtools/ok-timeline.ok.js +1 -0
  189. package/public/vendor/okjs/toolkit/docs/doc-viewer.ok.js +1 -0
  190. package/public/vendor/okjs/toolkit/editor/ok-live-component.ok.js +1 -0
  191. package/public/vendor/okjs/toolkit/editor/ok-live-example.ok.js +1 -0
  192. package/public/vendor/okjs/toolkit/editor/ok-live-shell.ok.js +1 -0
  193. package/public/vendor/okjs/toolkit/elements/ok-dropdown.ok.js +1 -0
  194. package/public/vendor/okjs/toolkit/elements/ok-icon.ok.js +1 -0
  195. package/public/vendor/okjs/toolkit/elements/ok-select.ok.js +1 -0
  196. package/public/vendor/okjs/toolkit/elements/ok-table.ok.js +1 -0
  197. package/public/vendor/okjs/toolkit/errors/ok-crash-boundary.ok.js +1 -0
  198. package/public/vendor/okjs/toolkit/errors/ok-crash-info.ok.js +1 -0
  199. package/public/vendor/okjs/toolkit/i18n/i18n-editor-examples.ok.js +1 -0
  200. package/public/vendor/okjs/toolkit/i18n/i18n-editor.ok.js +1 -0
  201. package/public/vendor/okjs/toolkit/i18n/i18n-key-editor-tips.ok.js +1 -0
  202. package/public/vendor/okjs/toolkit/i18n/i18n-key-editor.ok.js +1 -0
  203. package/public/vendor/okjs/toolkit/i18n/i18n-service.ok.js +1 -0
  204. package/public/vendor/okjs/toolkit/i18n/ok-i18n.js +1 -0
  205. package/public/vendor/okjs/toolkit/interaction/ok-contextmenu.ok.js +1 -0
  206. package/public/vendor/okjs/toolkit/interaction/ok-dismiss-service.ok.js +1 -0
  207. package/public/vendor/okjs/toolkit/interaction/ok-draggable.ok.js +1 -0
  208. package/public/vendor/okjs/toolkit/interaction/ok-flyover-panel.ok.js +1 -0
  209. package/public/vendor/okjs/toolkit/interaction/ok-flyover.ok.js +1 -0
  210. package/public/vendor/okjs/toolkit/interaction/ok-highlight-service.ok.js +1 -0
  211. package/public/vendor/okjs/toolkit/interaction/ok-highlight.ok.js +1 -0
  212. package/public/vendor/okjs/toolkit/interaction/ok-hotkeys-service.ok.js +1 -0
  213. package/public/vendor/okjs/toolkit/interaction/ok-picker-service.ok.js +1 -0
  214. package/public/vendor/okjs/toolkit/interaction/ok-popout-service.ok.js +1 -0
  215. package/public/vendor/okjs/toolkit/interaction/ok-popout.ok.js +1 -0
  216. package/public/vendor/okjs/toolkit/interaction/ok-portal-service.ok.js +1 -0
  217. package/public/vendor/okjs/toolkit/interaction/ok-submenu.ok.js +1 -0
  218. package/public/vendor/okjs/toolkit/layout/ok-resizer.ok.js +1 -0
  219. package/public/vendor/okjs/toolkit/manifest.js +1 -0
  220. package/public/vendor/okjs/toolkit/modals/ok-modal-service.ok.js +1 -0
  221. package/public/vendor/okjs/toolkit/modals/ok-modal.ok.js +1 -0
  222. package/public/vendor/okjs/toolkit/modals/ok-toast-service.ok.js +1 -0
  223. package/public/vendor/okjs/toolkit/modals/ok-window.ok.js +1 -0
  224. package/public/vendor/okjs/toolkit/navigation/hash-router.ok.js +1 -0
  225. package/public/vendor/okjs/toolkit/navigation/history-router.ok.js +1 -0
  226. package/public/vendor/okjs/toolkit/navigation/router.ok.js +1 -0
  227. package/public/vendor/okjs/toolkit/sync/reactive-sync-service.ok.js +1 -0
  228. package/public/vendor/okjs/toolkit/sync/reactive-sync.js +1 -0
  229. package/public/vendor/okjs/toolkit/theming/blocks/ok-design-button.css.js +1 -0
  230. package/public/vendor/okjs/toolkit/theming/blocks/ok-design-input.css.js +1 -0
  231. package/public/vendor/okjs/toolkit/theming/ok-design-style.js +1 -0
  232. package/public/vendor/okjs/toolkit/theming/ok-design-vars.js +1 -0
  233. package/public/vendor/okjs/toolkit/theming/ok-design.ok.js +1 -0
  234. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-input.ok.js +1 -0
  235. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-service.ok.js +1 -0
  236. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-style.js +1 -0
  237. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-utils.js +1 -0
  238. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-variable.ok.js +1 -0
  239. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor-vm.js +1 -0
  240. package/public/vendor/okjs/toolkit/theming/ok-theme-editor/ok-theme-editor.ok.js +1 -0
  241. package/public/vendor/okjs/toolkit/theming/ok-theme.ok.js +1 -0
  242. package/public/view-models/type-query-vm.js +1 -0
  243. package/types/environment.d.ts +130 -0
  244. package/types/errors.d.ts +98 -0
  245. package/types/features/admin.d.ts +4 -0
  246. package/types/features/api.d.ts +18 -0
  247. package/types/features/auth.d.ts +31 -0
  248. package/types/features/embeddings.d.ts +39 -0
  249. package/types/features/engines.d.ts +23 -0
  250. package/types/features/files.d.ts +29 -0
  251. package/types/features/fts.d.ts +25 -0
  252. package/types/features/functions.d.ts +23 -0
  253. package/types/features/mcp.d.ts +17 -0
  254. package/types/features/pipelines.d.ts +19 -0
  255. package/types/features/queue.d.ts +189 -0
  256. package/types/features/sync.d.ts +34 -0
  257. package/types/features/time-machine.d.ts +32 -0
  258. package/types/features/views.d.ts +16 -0
  259. package/types/index.d.ts +153 -0
  260. 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.