@stoneforge/quarry 0.1.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 (330) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +160 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +8 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/quarry-api.d.ts +268 -0
  8. package/dist/api/quarry-api.d.ts.map +1 -0
  9. package/dist/api/quarry-api.js +3905 -0
  10. package/dist/api/quarry-api.js.map +1 -0
  11. package/dist/api/types.d.ts +1359 -0
  12. package/dist/api/types.d.ts.map +1 -0
  13. package/dist/api/types.js +204 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/bin/sf.d.ts +3 -0
  16. package/dist/bin/sf.d.ts.map +1 -0
  17. package/dist/bin/sf.js +9 -0
  18. package/dist/bin/sf.js.map +1 -0
  19. package/dist/cli/commands/admin.d.ts +11 -0
  20. package/dist/cli/commands/admin.d.ts.map +1 -0
  21. package/dist/cli/commands/admin.js +465 -0
  22. package/dist/cli/commands/admin.js.map +1 -0
  23. package/dist/cli/commands/alias.d.ts +8 -0
  24. package/dist/cli/commands/alias.d.ts.map +1 -0
  25. package/dist/cli/commands/alias.js +70 -0
  26. package/dist/cli/commands/alias.js.map +1 -0
  27. package/dist/cli/commands/channel.d.ts +13 -0
  28. package/dist/cli/commands/channel.d.ts.map +1 -0
  29. package/dist/cli/commands/channel.js +680 -0
  30. package/dist/cli/commands/channel.js.map +1 -0
  31. package/dist/cli/commands/completion.d.ts +8 -0
  32. package/dist/cli/commands/completion.d.ts.map +1 -0
  33. package/dist/cli/commands/completion.js +87 -0
  34. package/dist/cli/commands/completion.js.map +1 -0
  35. package/dist/cli/commands/config.d.ts +12 -0
  36. package/dist/cli/commands/config.d.ts.map +1 -0
  37. package/dist/cli/commands/config.js +242 -0
  38. package/dist/cli/commands/config.js.map +1 -0
  39. package/dist/cli/commands/crud.d.ts +64 -0
  40. package/dist/cli/commands/crud.d.ts.map +1 -0
  41. package/dist/cli/commands/crud.js +805 -0
  42. package/dist/cli/commands/crud.js.map +1 -0
  43. package/dist/cli/commands/dep.d.ts +16 -0
  44. package/dist/cli/commands/dep.d.ts.map +1 -0
  45. package/dist/cli/commands/dep.js +499 -0
  46. package/dist/cli/commands/dep.js.map +1 -0
  47. package/dist/cli/commands/document.d.ts +12 -0
  48. package/dist/cli/commands/document.d.ts.map +1 -0
  49. package/dist/cli/commands/document.js +1039 -0
  50. package/dist/cli/commands/document.js.map +1 -0
  51. package/dist/cli/commands/embeddings.d.ts +12 -0
  52. package/dist/cli/commands/embeddings.d.ts.map +1 -0
  53. package/dist/cli/commands/embeddings.js +273 -0
  54. package/dist/cli/commands/embeddings.js.map +1 -0
  55. package/dist/cli/commands/entity.d.ts +16 -0
  56. package/dist/cli/commands/entity.d.ts.map +1 -0
  57. package/dist/cli/commands/entity.js +522 -0
  58. package/dist/cli/commands/entity.js.map +1 -0
  59. package/dist/cli/commands/gc.d.ts +10 -0
  60. package/dist/cli/commands/gc.d.ts.map +1 -0
  61. package/dist/cli/commands/gc.js +257 -0
  62. package/dist/cli/commands/gc.js.map +1 -0
  63. package/dist/cli/commands/help.d.ts +11 -0
  64. package/dist/cli/commands/help.d.ts.map +1 -0
  65. package/dist/cli/commands/help.js +169 -0
  66. package/dist/cli/commands/help.js.map +1 -0
  67. package/dist/cli/commands/history.d.ts +9 -0
  68. package/dist/cli/commands/history.d.ts.map +1 -0
  69. package/dist/cli/commands/history.js +160 -0
  70. package/dist/cli/commands/history.js.map +1 -0
  71. package/dist/cli/commands/identity.d.ts +18 -0
  72. package/dist/cli/commands/identity.d.ts.map +1 -0
  73. package/dist/cli/commands/identity.js +698 -0
  74. package/dist/cli/commands/identity.js.map +1 -0
  75. package/dist/cli/commands/inbox.d.ts +20 -0
  76. package/dist/cli/commands/inbox.d.ts.map +1 -0
  77. package/dist/cli/commands/inbox.js +493 -0
  78. package/dist/cli/commands/inbox.js.map +1 -0
  79. package/dist/cli/commands/init.d.ts +20 -0
  80. package/dist/cli/commands/init.d.ts.map +1 -0
  81. package/dist/cli/commands/init.js +144 -0
  82. package/dist/cli/commands/init.js.map +1 -0
  83. package/dist/cli/commands/install.d.ts +9 -0
  84. package/dist/cli/commands/install.d.ts.map +1 -0
  85. package/dist/cli/commands/install.js +200 -0
  86. package/dist/cli/commands/install.js.map +1 -0
  87. package/dist/cli/commands/library.d.ts +12 -0
  88. package/dist/cli/commands/library.d.ts.map +1 -0
  89. package/dist/cli/commands/library.js +665 -0
  90. package/dist/cli/commands/library.js.map +1 -0
  91. package/dist/cli/commands/message.d.ts +11 -0
  92. package/dist/cli/commands/message.d.ts.map +1 -0
  93. package/dist/cli/commands/message.js +608 -0
  94. package/dist/cli/commands/message.js.map +1 -0
  95. package/dist/cli/commands/plan.d.ts +17 -0
  96. package/dist/cli/commands/plan.d.ts.map +1 -0
  97. package/dist/cli/commands/plan.js +698 -0
  98. package/dist/cli/commands/plan.js.map +1 -0
  99. package/dist/cli/commands/playbook.d.ts +12 -0
  100. package/dist/cli/commands/playbook.d.ts.map +1 -0
  101. package/dist/cli/commands/playbook.js +730 -0
  102. package/dist/cli/commands/playbook.js.map +1 -0
  103. package/dist/cli/commands/reset.d.ts +12 -0
  104. package/dist/cli/commands/reset.d.ts.map +1 -0
  105. package/dist/cli/commands/reset.js +306 -0
  106. package/dist/cli/commands/reset.js.map +1 -0
  107. package/dist/cli/commands/serve.d.ts +11 -0
  108. package/dist/cli/commands/serve.d.ts.map +1 -0
  109. package/dist/cli/commands/serve.js +106 -0
  110. package/dist/cli/commands/serve.js.map +1 -0
  111. package/dist/cli/commands/stats.d.ts +8 -0
  112. package/dist/cli/commands/stats.d.ts.map +1 -0
  113. package/dist/cli/commands/stats.js +82 -0
  114. package/dist/cli/commands/stats.js.map +1 -0
  115. package/dist/cli/commands/sync.d.ts +14 -0
  116. package/dist/cli/commands/sync.d.ts.map +1 -0
  117. package/dist/cli/commands/sync.js +370 -0
  118. package/dist/cli/commands/sync.js.map +1 -0
  119. package/dist/cli/commands/task.d.ts +25 -0
  120. package/dist/cli/commands/task.d.ts.map +1 -0
  121. package/dist/cli/commands/task.js +1153 -0
  122. package/dist/cli/commands/task.js.map +1 -0
  123. package/dist/cli/commands/team.d.ts +13 -0
  124. package/dist/cli/commands/team.d.ts.map +1 -0
  125. package/dist/cli/commands/team.js +471 -0
  126. package/dist/cli/commands/team.js.map +1 -0
  127. package/dist/cli/commands/workflow.d.ts +16 -0
  128. package/dist/cli/commands/workflow.d.ts.map +1 -0
  129. package/dist/cli/commands/workflow.js +753 -0
  130. package/dist/cli/commands/workflow.js.map +1 -0
  131. package/dist/cli/completion.d.ts +28 -0
  132. package/dist/cli/completion.d.ts.map +1 -0
  133. package/dist/cli/completion.js +295 -0
  134. package/dist/cli/completion.js.map +1 -0
  135. package/dist/cli/db.d.ts +38 -0
  136. package/dist/cli/db.d.ts.map +1 -0
  137. package/dist/cli/db.js +90 -0
  138. package/dist/cli/db.js.map +1 -0
  139. package/dist/cli/formatter.d.ts +87 -0
  140. package/dist/cli/formatter.d.ts.map +1 -0
  141. package/dist/cli/formatter.js +464 -0
  142. package/dist/cli/formatter.js.map +1 -0
  143. package/dist/cli/index.d.ts +33 -0
  144. package/dist/cli/index.d.ts.map +1 -0
  145. package/dist/cli/index.js +38 -0
  146. package/dist/cli/index.js.map +1 -0
  147. package/dist/cli/parser.d.ts +45 -0
  148. package/dist/cli/parser.d.ts.map +1 -0
  149. package/dist/cli/parser.js +256 -0
  150. package/dist/cli/parser.js.map +1 -0
  151. package/dist/cli/plugin-loader.d.ts +39 -0
  152. package/dist/cli/plugin-loader.d.ts.map +1 -0
  153. package/dist/cli/plugin-loader.js +165 -0
  154. package/dist/cli/plugin-loader.js.map +1 -0
  155. package/dist/cli/plugin-registry.d.ts +50 -0
  156. package/dist/cli/plugin-registry.d.ts.map +1 -0
  157. package/dist/cli/plugin-registry.js +206 -0
  158. package/dist/cli/plugin-registry.js.map +1 -0
  159. package/dist/cli/plugin-types.d.ts +106 -0
  160. package/dist/cli/plugin-types.d.ts.map +1 -0
  161. package/dist/cli/plugin-types.js +103 -0
  162. package/dist/cli/plugin-types.js.map +1 -0
  163. package/dist/cli/runner.d.ts +35 -0
  164. package/dist/cli/runner.d.ts.map +1 -0
  165. package/dist/cli/runner.js +340 -0
  166. package/dist/cli/runner.js.map +1 -0
  167. package/dist/cli/suggest.d.ts +15 -0
  168. package/dist/cli/suggest.d.ts.map +1 -0
  169. package/dist/cli/suggest.js +49 -0
  170. package/dist/cli/suggest.js.map +1 -0
  171. package/dist/cli/types.d.ts +138 -0
  172. package/dist/cli/types.d.ts.map +1 -0
  173. package/dist/cli/types.js +63 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/config/config.d.ts +86 -0
  176. package/dist/config/config.d.ts.map +1 -0
  177. package/dist/config/config.js +348 -0
  178. package/dist/config/config.js.map +1 -0
  179. package/dist/config/defaults.d.ts +66 -0
  180. package/dist/config/defaults.d.ts.map +1 -0
  181. package/dist/config/defaults.js +114 -0
  182. package/dist/config/defaults.js.map +1 -0
  183. package/dist/config/duration.d.ts +75 -0
  184. package/dist/config/duration.d.ts.map +1 -0
  185. package/dist/config/duration.js +190 -0
  186. package/dist/config/duration.js.map +1 -0
  187. package/dist/config/env.d.ts +67 -0
  188. package/dist/config/env.d.ts.map +1 -0
  189. package/dist/config/env.js +207 -0
  190. package/dist/config/env.js.map +1 -0
  191. package/dist/config/file.d.ts +97 -0
  192. package/dist/config/file.d.ts.map +1 -0
  193. package/dist/config/file.js +365 -0
  194. package/dist/config/file.js.map +1 -0
  195. package/dist/config/index.d.ts +35 -0
  196. package/dist/config/index.d.ts.map +1 -0
  197. package/dist/config/index.js +41 -0
  198. package/dist/config/index.js.map +1 -0
  199. package/dist/config/merge.d.ts +53 -0
  200. package/dist/config/merge.d.ts.map +1 -0
  201. package/dist/config/merge.js +226 -0
  202. package/dist/config/merge.js.map +1 -0
  203. package/dist/config/types.d.ts +257 -0
  204. package/dist/config/types.d.ts.map +1 -0
  205. package/dist/config/types.js +72 -0
  206. package/dist/config/types.js.map +1 -0
  207. package/dist/config/validation.d.ts +55 -0
  208. package/dist/config/validation.d.ts.map +1 -0
  209. package/dist/config/validation.js +251 -0
  210. package/dist/config/validation.js.map +1 -0
  211. package/dist/http/index.d.ts +8 -0
  212. package/dist/http/index.d.ts.map +1 -0
  213. package/dist/http/index.js +12 -0
  214. package/dist/http/index.js.map +1 -0
  215. package/dist/http/sync-handlers.d.ts +162 -0
  216. package/dist/http/sync-handlers.d.ts.map +1 -0
  217. package/dist/http/sync-handlers.js +271 -0
  218. package/dist/http/sync-handlers.js.map +1 -0
  219. package/dist/index.d.ts +25 -0
  220. package/dist/index.d.ts.map +1 -0
  221. package/dist/index.js +69 -0
  222. package/dist/index.js.map +1 -0
  223. package/dist/server/index.d.ts +34 -0
  224. package/dist/server/index.d.ts.map +1 -0
  225. package/dist/server/index.js +3329 -0
  226. package/dist/server/index.js.map +1 -0
  227. package/dist/server/static.d.ts +18 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +71 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/ws/broadcaster.d.ts +8 -0
  232. package/dist/server/ws/broadcaster.d.ts.map +1 -0
  233. package/dist/server/ws/broadcaster.js +7 -0
  234. package/dist/server/ws/broadcaster.js.map +1 -0
  235. package/dist/server/ws/handler.d.ts +55 -0
  236. package/dist/server/ws/handler.d.ts.map +1 -0
  237. package/dist/server/ws/handler.js +160 -0
  238. package/dist/server/ws/handler.js.map +1 -0
  239. package/dist/services/blocked-cache.d.ts +297 -0
  240. package/dist/services/blocked-cache.d.ts.map +1 -0
  241. package/dist/services/blocked-cache.js +755 -0
  242. package/dist/services/blocked-cache.js.map +1 -0
  243. package/dist/services/dependency.d.ts +205 -0
  244. package/dist/services/dependency.d.ts.map +1 -0
  245. package/dist/services/dependency.js +566 -0
  246. package/dist/services/dependency.js.map +1 -0
  247. package/dist/services/embeddings/fusion.d.ts +33 -0
  248. package/dist/services/embeddings/fusion.d.ts.map +1 -0
  249. package/dist/services/embeddings/fusion.js +34 -0
  250. package/dist/services/embeddings/fusion.js.map +1 -0
  251. package/dist/services/embeddings/index.d.ts +12 -0
  252. package/dist/services/embeddings/index.d.ts.map +1 -0
  253. package/dist/services/embeddings/index.js +10 -0
  254. package/dist/services/embeddings/index.js.map +1 -0
  255. package/dist/services/embeddings/local-provider.d.ts +31 -0
  256. package/dist/services/embeddings/local-provider.d.ts.map +1 -0
  257. package/dist/services/embeddings/local-provider.js +80 -0
  258. package/dist/services/embeddings/local-provider.js.map +1 -0
  259. package/dist/services/embeddings/service.d.ts +76 -0
  260. package/dist/services/embeddings/service.d.ts.map +1 -0
  261. package/dist/services/embeddings/service.js +153 -0
  262. package/dist/services/embeddings/service.js.map +1 -0
  263. package/dist/services/embeddings/types.d.ts +70 -0
  264. package/dist/services/embeddings/types.d.ts.map +1 -0
  265. package/dist/services/embeddings/types.js +8 -0
  266. package/dist/services/embeddings/types.js.map +1 -0
  267. package/dist/services/id-length-cache.d.ts +156 -0
  268. package/dist/services/id-length-cache.d.ts.map +1 -0
  269. package/dist/services/id-length-cache.js +197 -0
  270. package/dist/services/id-length-cache.js.map +1 -0
  271. package/dist/services/inbox.d.ts +147 -0
  272. package/dist/services/inbox.d.ts.map +1 -0
  273. package/dist/services/inbox.js +428 -0
  274. package/dist/services/inbox.js.map +1 -0
  275. package/dist/services/index.d.ts +10 -0
  276. package/dist/services/index.d.ts.map +1 -0
  277. package/dist/services/index.js +10 -0
  278. package/dist/services/index.js.map +1 -0
  279. package/dist/services/priority-service.d.ts +145 -0
  280. package/dist/services/priority-service.d.ts.map +1 -0
  281. package/dist/services/priority-service.js +272 -0
  282. package/dist/services/priority-service.js.map +1 -0
  283. package/dist/services/search-utils.d.ts +47 -0
  284. package/dist/services/search-utils.d.ts.map +1 -0
  285. package/dist/services/search-utils.js +83 -0
  286. package/dist/services/search-utils.js.map +1 -0
  287. package/dist/sync/hash.d.ts +48 -0
  288. package/dist/sync/hash.d.ts.map +1 -0
  289. package/dist/sync/hash.js +136 -0
  290. package/dist/sync/hash.js.map +1 -0
  291. package/dist/sync/index.d.ts +11 -0
  292. package/dist/sync/index.d.ts.map +1 -0
  293. package/dist/sync/index.js +16 -0
  294. package/dist/sync/index.js.map +1 -0
  295. package/dist/sync/merge.d.ts +80 -0
  296. package/dist/sync/merge.d.ts.map +1 -0
  297. package/dist/sync/merge.js +310 -0
  298. package/dist/sync/merge.js.map +1 -0
  299. package/dist/sync/serialization.d.ts +132 -0
  300. package/dist/sync/serialization.d.ts.map +1 -0
  301. package/dist/sync/serialization.js +306 -0
  302. package/dist/sync/serialization.js.map +1 -0
  303. package/dist/sync/service.d.ts +102 -0
  304. package/dist/sync/service.d.ts.map +1 -0
  305. package/dist/sync/service.js +493 -0
  306. package/dist/sync/service.js.map +1 -0
  307. package/dist/sync/types.d.ts +275 -0
  308. package/dist/sync/types.d.ts.map +1 -0
  309. package/dist/sync/types.js +76 -0
  310. package/dist/sync/types.js.map +1 -0
  311. package/dist/systems/identity.d.ts +479 -0
  312. package/dist/systems/identity.d.ts.map +1 -0
  313. package/dist/systems/identity.js +817 -0
  314. package/dist/systems/identity.js.map +1 -0
  315. package/dist/systems/index.d.ts +8 -0
  316. package/dist/systems/index.d.ts.map +1 -0
  317. package/dist/systems/index.js +29 -0
  318. package/dist/systems/index.js.map +1 -0
  319. package/package.json +121 -0
  320. package/web/assets/charts-vendor-D1YcbGux.js +55 -0
  321. package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
  322. package/web/assets/editor-vendor-BxraAWts.js +279 -0
  323. package/web/assets/index-B77vv208.js +341 -0
  324. package/web/assets/index-CF_XnVLh.css +1 -0
  325. package/web/assets/router-vendor-BCKpRBrB.js +41 -0
  326. package/web/assets/ui-vendor-DUahGnbT.js +45 -0
  327. package/web/assets/utils-vendor-CfYKiENT.js +813 -0
  328. package/web/favicon.ico +0 -0
  329. package/web/index.html +23 -0
  330. package/web/logo.png +0 -0
@@ -0,0 +1,817 @@
1
+ /**
2
+ * Identity System - Authentication and Signature Verification
3
+ *
4
+ * The identity system manages entity authentication and verification, supporting
5
+ * both soft (name-based) and cryptographic (key-based) identity models.
6
+ *
7
+ * Features:
8
+ * - Identity mode configuration (soft, cryptographic, hybrid)
9
+ * - Ed25519 signature generation and verification
10
+ * - Signed request validation with time tolerance
11
+ * - Actor context management
12
+ */
13
+ import { IdentityError, ValidationError, ErrorCode, isValidTimestamp } from '@stoneforge/core';
14
+ // ============================================================================
15
+ // Identity Mode Types
16
+ // ============================================================================
17
+ /**
18
+ * Identity mode determines the level of verification required
19
+ */
20
+ export const IdentityMode = {
21
+ /** Name-based identity without verification (default) */
22
+ SOFT: 'soft',
23
+ /** Key-based identity with signature verification */
24
+ CRYPTOGRAPHIC: 'cryptographic',
25
+ /** Mixed mode - accepts both verified and unverified actors */
26
+ HYBRID: 'hybrid',
27
+ };
28
+ // ============================================================================
29
+ // Verification Types
30
+ // ============================================================================
31
+ /**
32
+ * Result of signature verification
33
+ */
34
+ export const VerificationStatus = {
35
+ /** Signature is valid */
36
+ VALID: 'valid',
37
+ /** Signature is invalid or doesn't match */
38
+ INVALID: 'invalid',
39
+ /** Signature has expired (outside time tolerance) */
40
+ EXPIRED: 'expired',
41
+ /** Entity not found for verification */
42
+ ACTOR_NOT_FOUND: 'actor_not_found',
43
+ /** Entity has no public key */
44
+ NO_PUBLIC_KEY: 'no_public_key',
45
+ /** Signature was not provided */
46
+ NOT_SIGNED: 'not_signed',
47
+ };
48
+ /** Default time tolerance: 5 minutes in milliseconds */
49
+ export const DEFAULT_TIME_TOLERANCE = 5 * 60 * 1000;
50
+ /**
51
+ * Default identity configuration
52
+ */
53
+ export const DEFAULT_IDENTITY_CONFIG = {
54
+ mode: IdentityMode.SOFT,
55
+ timeTolerance: DEFAULT_TIME_TOLERANCE,
56
+ allowUnregisteredActors: true,
57
+ };
58
+ // ============================================================================
59
+ // Validation Constants
60
+ // ============================================================================
61
+ /** Base64 pattern for Ed25519 public keys (44 characters for 32 bytes) */
62
+ const PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]{43}=$/;
63
+ /** Base64 pattern for Ed25519 signatures (88 characters for 64 bytes) */
64
+ const SIGNATURE_PATTERN = /^[A-Za-z0-9+/]{86}==$/;
65
+ /** Minimum request hash length (SHA256 hex = 64 characters) */
66
+ const MIN_REQUEST_HASH_LENGTH = 64;
67
+ /** Request hash pattern (hex-encoded SHA256) */
68
+ const REQUEST_HASH_PATTERN = /^[a-f0-9]{64}$/i;
69
+ // ============================================================================
70
+ // Validation Functions
71
+ // ============================================================================
72
+ /**
73
+ * Validates an identity mode value
74
+ */
75
+ export function isValidIdentityMode(value) {
76
+ return (typeof value === 'string' &&
77
+ Object.values(IdentityMode).includes(value));
78
+ }
79
+ /**
80
+ * Validates identity mode and throws if invalid
81
+ */
82
+ export function validateIdentityMode(value) {
83
+ if (!isValidIdentityMode(value)) {
84
+ throw new ValidationError(`Invalid identity mode: ${value}. Must be one of: ${Object.values(IdentityMode).join(', ')}`, ErrorCode.INVALID_INPUT, { field: 'mode', value, expected: Object.values(IdentityMode) });
85
+ }
86
+ return value;
87
+ }
88
+ /**
89
+ * Validates a base64-encoded Ed25519 public key format
90
+ */
91
+ export function isValidPublicKey(value) {
92
+ if (typeof value !== 'string') {
93
+ return false;
94
+ }
95
+ return PUBLIC_KEY_PATTERN.test(value);
96
+ }
97
+ /**
98
+ * Validates a public key and throws if invalid format
99
+ */
100
+ export function validatePublicKey(value) {
101
+ if (typeof value !== 'string') {
102
+ throw new IdentityError('Public key must be a string', ErrorCode.INVALID_PUBLIC_KEY, { field: 'publicKey', value, expected: 'string' });
103
+ }
104
+ if (!PUBLIC_KEY_PATTERN.test(value)) {
105
+ throw new IdentityError('Invalid public key format. Expected base64-encoded Ed25519 public key (44 characters)', ErrorCode.INVALID_PUBLIC_KEY, { field: 'publicKey', value, expected: '44-character base64 string ending with =' });
106
+ }
107
+ return value;
108
+ }
109
+ /**
110
+ * Validates a base64-encoded Ed25519 signature format
111
+ */
112
+ export function isValidSignature(value) {
113
+ if (typeof value !== 'string') {
114
+ return false;
115
+ }
116
+ return SIGNATURE_PATTERN.test(value);
117
+ }
118
+ /**
119
+ * Validates a signature and throws if invalid format
120
+ */
121
+ export function validateSignature(value) {
122
+ if (typeof value !== 'string') {
123
+ throw new IdentityError('Signature must be a string', ErrorCode.INVALID_SIGNATURE, { field: 'signature', value, expected: 'string' });
124
+ }
125
+ if (!SIGNATURE_PATTERN.test(value)) {
126
+ throw new IdentityError('Invalid signature format. Expected base64-encoded Ed25519 signature (88 characters)', ErrorCode.INVALID_SIGNATURE, { field: 'signature', value, expected: '88-character base64 string ending with ==' });
127
+ }
128
+ return value;
129
+ }
130
+ /**
131
+ * Validates a request hash (SHA256 hex)
132
+ */
133
+ export function isValidRequestHash(value) {
134
+ if (typeof value !== 'string') {
135
+ return false;
136
+ }
137
+ if (value.length < MIN_REQUEST_HASH_LENGTH) {
138
+ return false;
139
+ }
140
+ return REQUEST_HASH_PATTERN.test(value);
141
+ }
142
+ /**
143
+ * Validates a request hash and throws if invalid
144
+ */
145
+ export function validateRequestHash(value) {
146
+ if (typeof value !== 'string') {
147
+ throw new ValidationError('Request hash must be a string', ErrorCode.INVALID_INPUT, { field: 'requestHash', value, expected: 'string' });
148
+ }
149
+ if (!REQUEST_HASH_PATTERN.test(value)) {
150
+ throw new ValidationError('Invalid request hash format. Expected 64-character hex-encoded SHA256 hash', ErrorCode.INVALID_INPUT, { field: 'requestHash', value, expected: '64-character hex string' });
151
+ }
152
+ return value;
153
+ }
154
+ /**
155
+ * Validates time tolerance value
156
+ */
157
+ export function isValidTimeTolerance(value) {
158
+ return (typeof value === 'number' &&
159
+ Number.isFinite(value) &&
160
+ value > 0 &&
161
+ value <= 24 * 60 * 60 * 1000 // Max 24 hours
162
+ );
163
+ }
164
+ /**
165
+ * Validates time tolerance and throws if invalid
166
+ */
167
+ export function validateTimeTolerance(value) {
168
+ if (!isValidTimeTolerance(value)) {
169
+ throw new ValidationError('Time tolerance must be a positive number (max 24 hours in milliseconds)', ErrorCode.INVALID_INPUT, { field: 'timeTolerance', value, expected: 'positive number <= 86400000' });
170
+ }
171
+ return value;
172
+ }
173
+ // ============================================================================
174
+ // Type Guards
175
+ // ============================================================================
176
+ /**
177
+ * Type guard for SignedRequestFields
178
+ */
179
+ export function isSignedRequestFields(value) {
180
+ if (typeof value !== 'object' || value === null) {
181
+ return false;
182
+ }
183
+ const obj = value;
184
+ return (isValidSignature(obj.signature) &&
185
+ isValidTimestamp(obj.signedAt) &&
186
+ typeof obj.actor === 'string' &&
187
+ obj.actor.length > 0);
188
+ }
189
+ /**
190
+ * Validates SignedRequestFields and throws detailed errors
191
+ */
192
+ export function validateSignedRequestFields(value) {
193
+ if (typeof value !== 'object' || value === null) {
194
+ throw new ValidationError('Signed request fields must be an object', ErrorCode.INVALID_INPUT, { value });
195
+ }
196
+ const obj = value;
197
+ // Validate signature
198
+ validateSignature(obj.signature);
199
+ // Validate signedAt
200
+ if (!isValidTimestamp(obj.signedAt)) {
201
+ throw new ValidationError('signedAt must be a valid ISO 8601 timestamp', ErrorCode.INVALID_TIMESTAMP, { field: 'signedAt', value: obj.signedAt });
202
+ }
203
+ // Validate actor
204
+ if (typeof obj.actor !== 'string' || obj.actor.length === 0) {
205
+ throw new ValidationError('Actor must be a non-empty string', ErrorCode.INVALID_INPUT, { field: 'actor', value: obj.actor });
206
+ }
207
+ return value;
208
+ }
209
+ /**
210
+ * Type guard for VerificationResult
211
+ */
212
+ export function isVerificationResult(value) {
213
+ if (typeof value !== 'object' || value === null) {
214
+ return false;
215
+ }
216
+ const obj = value;
217
+ return (typeof obj.status === 'string' &&
218
+ Object.values(VerificationStatus).includes(obj.status) &&
219
+ typeof obj.allowed === 'boolean');
220
+ }
221
+ /**
222
+ * Type guard for IdentityConfig
223
+ */
224
+ export function isIdentityConfig(value) {
225
+ if (typeof value !== 'object' || value === null) {
226
+ return false;
227
+ }
228
+ const obj = value;
229
+ return (isValidIdentityMode(obj.mode) &&
230
+ isValidTimeTolerance(obj.timeTolerance) &&
231
+ typeof obj.allowUnregisteredActors === 'boolean');
232
+ }
233
+ /**
234
+ * Validates IdentityConfig and throws detailed errors
235
+ */
236
+ export function validateIdentityConfig(value) {
237
+ if (typeof value !== 'object' || value === null) {
238
+ throw new ValidationError('Identity config must be an object', ErrorCode.INVALID_INPUT, { value });
239
+ }
240
+ const obj = value;
241
+ validateIdentityMode(obj.mode);
242
+ validateTimeTolerance(obj.timeTolerance);
243
+ if (typeof obj.allowUnregisteredActors !== 'boolean') {
244
+ throw new ValidationError('allowUnregisteredActors must be a boolean', ErrorCode.INVALID_INPUT, { field: 'allowUnregisteredActors', value: obj.allowUnregisteredActors });
245
+ }
246
+ return value;
247
+ }
248
+ // ============================================================================
249
+ // Signed Data Construction
250
+ // ============================================================================
251
+ /**
252
+ * Constructs the signed data string from components
253
+ * Format: actor|signedAt|requestHash
254
+ */
255
+ export function constructSignedData(data) {
256
+ return `${data.actor}|${data.signedAt}|${data.requestHash}`;
257
+ }
258
+ /**
259
+ * Parses a signed data string into components
260
+ * Format: actor|signedAt|requestHash
261
+ */
262
+ export function parseSignedData(signedDataString) {
263
+ const parts = signedDataString.split('|');
264
+ if (parts.length !== 3) {
265
+ throw new ValidationError('Invalid signed data format. Expected: actor|signedAt|requestHash', ErrorCode.INVALID_INPUT, { value: signedDataString, expected: 'actor|signedAt|requestHash' });
266
+ }
267
+ const [actor, signedAt, requestHash] = parts;
268
+ if (!actor || actor.length === 0) {
269
+ throw new ValidationError('Actor in signed data cannot be empty', ErrorCode.INVALID_INPUT, { field: 'actor', value: actor });
270
+ }
271
+ if (!isValidTimestamp(signedAt)) {
272
+ throw new ValidationError('Invalid signedAt timestamp in signed data', ErrorCode.INVALID_TIMESTAMP, { field: 'signedAt', value: signedAt });
273
+ }
274
+ validateRequestHash(requestHash);
275
+ return {
276
+ actor,
277
+ signedAt: signedAt,
278
+ requestHash,
279
+ };
280
+ }
281
+ // ============================================================================
282
+ // Time Tolerance Checking
283
+ // ============================================================================
284
+ /**
285
+ * Checks if a signature timestamp is within the allowed time tolerance
286
+ *
287
+ * @param signedAt - The timestamp when the request was signed
288
+ * @param tolerance - Time tolerance in milliseconds (default: 5 minutes)
289
+ * @param now - Current timestamp for testing (defaults to now)
290
+ * @returns Object with validity and age information
291
+ */
292
+ export function checkTimeTolerance(signedAt, tolerance = DEFAULT_TIME_TOLERANCE, now) {
293
+ const signedTime = new Date(signedAt).getTime();
294
+ const currentTime = (now ?? new Date()).getTime();
295
+ const ageMs = Math.abs(currentTime - signedTime);
296
+ if (ageMs > tolerance) {
297
+ return {
298
+ valid: false,
299
+ ageMs,
300
+ expiredBy: ageMs - tolerance,
301
+ };
302
+ }
303
+ return { valid: true, ageMs };
304
+ }
305
+ /**
306
+ * Checks time tolerance and throws if expired
307
+ */
308
+ export function validateTimeTolerance2(signedAt, tolerance = DEFAULT_TIME_TOLERANCE, now) {
309
+ const result = checkTimeTolerance(signedAt, tolerance, now);
310
+ if (!result.valid) {
311
+ throw new IdentityError(`Signature expired. Signed ${Math.round(result.ageMs / 1000)}s ago, tolerance is ${Math.round(tolerance / 1000)}s`, ErrorCode.SIGNATURE_EXPIRED, {
312
+ signedAt,
313
+ ageMs: result.ageMs,
314
+ tolerance,
315
+ expiredBy: result.expiredBy,
316
+ });
317
+ }
318
+ }
319
+ // ============================================================================
320
+ // Verification Result Factories
321
+ // ============================================================================
322
+ /**
323
+ * Creates a successful verification result
324
+ */
325
+ export function verificationSuccess(actor, ageMs) {
326
+ return {
327
+ status: VerificationStatus.VALID,
328
+ allowed: true,
329
+ actor,
330
+ details: ageMs !== undefined ? { signatureAgeMs: ageMs } : undefined,
331
+ };
332
+ }
333
+ /**
334
+ * Creates a failed verification result
335
+ */
336
+ export function verificationFailure(status, error, details) {
337
+ return {
338
+ status,
339
+ allowed: false,
340
+ error,
341
+ details,
342
+ };
343
+ }
344
+ /**
345
+ * Creates a "not signed" result (may be allowed in soft/hybrid mode)
346
+ */
347
+ export function verificationNotSigned(allowed, actor) {
348
+ return {
349
+ status: VerificationStatus.NOT_SIGNED,
350
+ allowed,
351
+ actor,
352
+ error: allowed ? undefined : 'Signature required in cryptographic mode',
353
+ };
354
+ }
355
+ // ============================================================================
356
+ // Ed25519 Cryptographic Operations
357
+ // ============================================================================
358
+ /**
359
+ * Verifies an Ed25519 signature using Bun's native crypto
360
+ *
361
+ * @param publicKey - Base64-encoded Ed25519 public key
362
+ * @param signature - Base64-encoded Ed25519 signature
363
+ * @param data - The data that was signed (as string or Uint8Array)
364
+ * @returns true if signature is valid, false otherwise
365
+ */
366
+ export async function verifyEd25519Signature(publicKey, signature, data) {
367
+ try {
368
+ // Decode base64 inputs
369
+ const publicKeyBytes = new Uint8Array(Buffer.from(publicKey, 'base64'));
370
+ const signatureBytes = new Uint8Array(Buffer.from(signature, 'base64'));
371
+ // Convert data to Uint8Array
372
+ const dataBytes = typeof data === 'string'
373
+ ? new TextEncoder().encode(data)
374
+ : new Uint8Array(data);
375
+ // Validate key and signature lengths
376
+ if (publicKeyBytes.length !== 32) {
377
+ return false;
378
+ }
379
+ if (signatureBytes.length !== 64) {
380
+ return false;
381
+ }
382
+ // Use Web Crypto API for Ed25519 verification
383
+ const cryptoKey = await crypto.subtle.importKey('raw', publicKeyBytes, { name: 'Ed25519' }, false, ['verify']);
384
+ return await crypto.subtle.verify({ name: 'Ed25519' }, cryptoKey, signatureBytes, dataBytes);
385
+ }
386
+ catch {
387
+ // Any crypto error means verification failed
388
+ return false;
389
+ }
390
+ }
391
+ /**
392
+ * Signs data using Ed25519 (for testing purposes)
393
+ * In production, signing should be done by the entity externally
394
+ *
395
+ * @param privateKey - Base64-encoded Ed25519 private key in PKCS8 format
396
+ * @param data - The data to sign
397
+ * @returns Base64-encoded signature
398
+ */
399
+ export async function signEd25519(privateKey, data) {
400
+ const privateKeyBytes = Buffer.from(privateKey, 'base64');
401
+ // Convert data to Uint8Array
402
+ const dataBytes = typeof data === 'string'
403
+ ? new TextEncoder().encode(data)
404
+ : new Uint8Array(data);
405
+ // Import the private key in PKCS8 format
406
+ const cryptoKey = await crypto.subtle.importKey('pkcs8', privateKeyBytes, { name: 'Ed25519' }, false, ['sign']);
407
+ // Sign the data
408
+ const signatureBytes = await crypto.subtle.sign({ name: 'Ed25519' }, cryptoKey, dataBytes);
409
+ // Return base64-encoded signature
410
+ return Buffer.from(signatureBytes).toString('base64');
411
+ }
412
+ /**
413
+ * Generates a new Ed25519 keypair (for testing purposes)
414
+ *
415
+ * @returns Object with base64-encoded public and private keys (PKCS8 format for private)
416
+ */
417
+ export async function generateEd25519Keypair() {
418
+ const keypair = await crypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify']);
419
+ // Export public key as raw bytes (32 bytes -> 44 base64 chars)
420
+ const publicKeyBuffer = await crypto.subtle.exportKey('raw', keypair.publicKey);
421
+ // Export private key in PKCS8 format (48 bytes -> 64 base64 chars)
422
+ const privateKeyBuffer = await crypto.subtle.exportKey('pkcs8', keypair.privateKey);
423
+ return {
424
+ publicKey: Buffer.from(new Uint8Array(publicKeyBuffer)).toString('base64'),
425
+ privateKey: Buffer.from(new Uint8Array(privateKeyBuffer)).toString('base64'),
426
+ };
427
+ }
428
+ /**
429
+ * Performs full signature verification pipeline
430
+ *
431
+ * 1. Validates signature format
432
+ * 2. Looks up entity's public key
433
+ * 3. Constructs signed data
434
+ * 4. Checks time tolerance
435
+ * 5. Verifies signature cryptographically
436
+ */
437
+ export async function verifySignature(options) {
438
+ const config = { ...DEFAULT_IDENTITY_CONFIG, ...options.config };
439
+ const { signedRequest, requestHash, lookupEntity, now } = options;
440
+ // 1. Validate request hash format
441
+ if (!isValidRequestHash(requestHash)) {
442
+ return verificationFailure(VerificationStatus.INVALID, 'Invalid request hash format');
443
+ }
444
+ // 2. Look up entity
445
+ const entity = await lookupEntity(signedRequest.actor);
446
+ if (!entity) {
447
+ return verificationFailure(VerificationStatus.ACTOR_NOT_FOUND, `Actor '${signedRequest.actor}' not found`, { entityFound: false });
448
+ }
449
+ // 3. Check for public key
450
+ if (!entity.publicKey) {
451
+ return verificationFailure(VerificationStatus.NO_PUBLIC_KEY, `Actor '${signedRequest.actor}' has no public key`, { entityFound: true, hasPublicKey: false });
452
+ }
453
+ // 4. Validate public key format
454
+ if (!isValidPublicKey(entity.publicKey)) {
455
+ return verificationFailure(VerificationStatus.INVALID, 'Entity has invalid public key format', { entityFound: true, hasPublicKey: true });
456
+ }
457
+ // 5. Check time tolerance
458
+ const timeCheck = checkTimeTolerance(signedRequest.signedAt, config.timeTolerance, now);
459
+ if (!timeCheck.valid) {
460
+ return verificationFailure(VerificationStatus.EXPIRED, `Signature expired. Age: ${Math.round(timeCheck.ageMs / 1000)}s, tolerance: ${Math.round(config.timeTolerance / 1000)}s`, { signatureAgeMs: timeCheck.ageMs });
461
+ }
462
+ // 6. Construct signed data and verify
463
+ const signedData = constructSignedData({
464
+ actor: signedRequest.actor,
465
+ signedAt: signedRequest.signedAt,
466
+ requestHash,
467
+ });
468
+ const isValid = await verifyEd25519Signature(entity.publicKey, signedRequest.signature, signedData);
469
+ if (!isValid) {
470
+ return verificationFailure(VerificationStatus.INVALID, 'Signature verification failed', { signatureAgeMs: timeCheck.ageMs, entityFound: true, hasPublicKey: true });
471
+ }
472
+ return verificationSuccess(signedRequest.actor, timeCheck.ageMs);
473
+ }
474
+ /**
475
+ * Determines if a request should be allowed based on identity mode
476
+ */
477
+ export function shouldAllowRequest(mode, verificationResult) {
478
+ switch (mode) {
479
+ case IdentityMode.SOFT:
480
+ // Always allow in soft mode
481
+ return true;
482
+ case IdentityMode.CRYPTOGRAPHIC:
483
+ // Only allow valid signatures
484
+ return verificationResult.status === VerificationStatus.VALID;
485
+ case IdentityMode.HYBRID:
486
+ // Allow valid signatures or unsigned requests
487
+ return (verificationResult.status === VerificationStatus.VALID ||
488
+ verificationResult.status === VerificationStatus.NOT_SIGNED);
489
+ default:
490
+ return false;
491
+ }
492
+ }
493
+ // ============================================================================
494
+ // Utility Functions
495
+ // ============================================================================
496
+ /**
497
+ * Creates a SHA256 hash of the request body for signing
498
+ */
499
+ export async function hashRequestBody(body) {
500
+ const data = typeof body === 'string' ? body : JSON.stringify(body);
501
+ const encoder = new TextEncoder();
502
+ const dataBuffer = encoder.encode(data);
503
+ const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
504
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
505
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
506
+ }
507
+ /**
508
+ * Creates a signed request from signing input
509
+ */
510
+ export async function createSignedRequest(input, privateKey, signedAt) {
511
+ const timestamp = signedAt ?? new Date().toISOString();
512
+ const signedData = constructSignedData({
513
+ actor: input.actor,
514
+ signedAt: timestamp,
515
+ requestHash: input.requestHash,
516
+ });
517
+ const signature = await signEd25519(privateKey, signedData);
518
+ return {
519
+ signature,
520
+ signedAt: timestamp,
521
+ actor: input.actor,
522
+ };
523
+ }
524
+ /**
525
+ * Merges partial config with defaults
526
+ */
527
+ export function createIdentityConfig(partial) {
528
+ return {
529
+ ...DEFAULT_IDENTITY_CONFIG,
530
+ ...partial,
531
+ };
532
+ }
533
+ // ============================================================================
534
+ // Actor Context Management (Phase 2: Soft Identity)
535
+ // ============================================================================
536
+ /**
537
+ * Sources from which actor identity can be determined
538
+ */
539
+ export const ActorSource = {
540
+ /** Explicitly provided in the operation */
541
+ EXPLICIT: 'explicit',
542
+ /** From CLI --actor flag */
543
+ CLI_FLAG: 'cli_flag',
544
+ /** From configuration file */
545
+ CONFIG: 'config',
546
+ /** From element's createdBy field (fallback) */
547
+ ELEMENT: 'element',
548
+ /** System-generated operations */
549
+ SYSTEM: 'system',
550
+ };
551
+ /**
552
+ * Resolves actor context from multiple sources
553
+ *
554
+ * Priority order (highest to lowest):
555
+ * 1. Explicit actor (provided in operation)
556
+ * 2. CLI actor (--actor flag)
557
+ * 3. Config actor (default actor in config)
558
+ * 4. Element's createdBy (fallback for updates/deletes)
559
+ *
560
+ * @param options - Resolution options with actor sources
561
+ * @returns The resolved actor context
562
+ * @throws ValidationError if no actor can be resolved
563
+ */
564
+ export function resolveActor(options) {
565
+ // Try each source in priority order
566
+ if (options.explicitActor) {
567
+ return {
568
+ actor: options.explicitActor,
569
+ source: ActorSource.EXPLICIT,
570
+ verified: false,
571
+ };
572
+ }
573
+ if (options.cliActor) {
574
+ return {
575
+ actor: options.cliActor,
576
+ source: ActorSource.CLI_FLAG,
577
+ verified: false,
578
+ };
579
+ }
580
+ if (options.configActor) {
581
+ return {
582
+ actor: options.configActor,
583
+ source: ActorSource.CONFIG,
584
+ verified: false,
585
+ };
586
+ }
587
+ if (options.elementCreatedBy) {
588
+ return {
589
+ actor: options.elementCreatedBy,
590
+ source: ActorSource.ELEMENT,
591
+ verified: false,
592
+ };
593
+ }
594
+ // No actor could be resolved
595
+ throw new ValidationError('No actor could be resolved. Provide an actor explicitly, via CLI flag (--actor), or in configuration.', ErrorCode.MISSING_REQUIRED_FIELD, { field: 'actor' });
596
+ }
597
+ /**
598
+ * Validates an actor in soft identity mode
599
+ *
600
+ * In soft mode:
601
+ * - Accepts any non-empty string as actor
602
+ * - Optionally checks if entity exists (if lookupEntity provided and allowUnregisteredActors is false)
603
+ * - Always returns verified: false
604
+ *
605
+ * @param actor - The actor name/ID to validate
606
+ * @param options - Validation options
607
+ * @returns Validation result with context if valid
608
+ */
609
+ export async function validateSoftActor(actor, options) {
610
+ const config = { ...DEFAULT_IDENTITY_CONFIG, ...options?.config };
611
+ // Basic validation - must be non-empty string
612
+ if (!actor || typeof actor !== 'string' || actor.trim().length === 0) {
613
+ return {
614
+ valid: false,
615
+ error: 'Actor must be a non-empty string',
616
+ };
617
+ }
618
+ // In soft mode with unregistered actors allowed, skip lookup
619
+ if (config.mode === IdentityMode.SOFT && config.allowUnregisteredActors) {
620
+ return {
621
+ valid: true,
622
+ context: {
623
+ actor,
624
+ source: ActorSource.EXPLICIT,
625
+ verified: false,
626
+ },
627
+ };
628
+ }
629
+ // If lookupEntity is provided and unregistered actors not allowed, verify entity exists
630
+ if (options?.lookupEntity && !config.allowUnregisteredActors) {
631
+ const entity = await options.lookupEntity(actor);
632
+ if (!entity) {
633
+ return {
634
+ valid: false,
635
+ error: `Actor '${actor}' not found and unregistered actors are not allowed`,
636
+ entityExists: false,
637
+ };
638
+ }
639
+ return {
640
+ valid: true,
641
+ context: {
642
+ actor,
643
+ source: ActorSource.EXPLICIT,
644
+ verified: false,
645
+ entityId: actor, // In soft mode, actor name is used as ID reference
646
+ },
647
+ entityExists: true,
648
+ };
649
+ }
650
+ // Default: accept the actor
651
+ return {
652
+ valid: true,
653
+ context: {
654
+ actor,
655
+ source: ActorSource.EXPLICIT,
656
+ verified: false,
657
+ },
658
+ };
659
+ }
660
+ /**
661
+ * Type guard for ActorContext
662
+ */
663
+ export function isActorContext(value) {
664
+ if (typeof value !== 'object' || value === null) {
665
+ return false;
666
+ }
667
+ const obj = value;
668
+ return (typeof obj.actor === 'string' &&
669
+ typeof obj.source === 'string' &&
670
+ Object.values(ActorSource).includes(obj.source) &&
671
+ typeof obj.verified === 'boolean');
672
+ }
673
+ /**
674
+ * Creates an actor context for system operations
675
+ */
676
+ export function createSystemActorContext() {
677
+ return {
678
+ actor: 'system',
679
+ source: ActorSource.SYSTEM,
680
+ verified: true, // System is always trusted
681
+ };
682
+ }
683
+ /**
684
+ * Creates an actor context from an explicit actor string
685
+ */
686
+ export function createActorContext(actor, source = ActorSource.EXPLICIT) {
687
+ return {
688
+ actor,
689
+ source,
690
+ verified: false,
691
+ };
692
+ }
693
+ /**
694
+ * Creates a verification middleware function
695
+ *
696
+ * The middleware:
697
+ * 1. Checks identity mode from config
698
+ * 2. In soft mode: allows all requests, extracts actor if provided
699
+ * 3. In cryptographic mode: requires valid signature
700
+ * 4. In hybrid mode: allows unsigned or validly signed requests
701
+ *
702
+ * @param options - Middleware options
703
+ * @returns A middleware function that verifies requests
704
+ *
705
+ * @example
706
+ * ```typescript
707
+ * const middleware = createVerificationMiddleware({
708
+ * lookupEntity: (actor) => api.lookupEntityByName(actor),
709
+ * config: { mode: IdentityMode.CRYPTOGRAPHIC }
710
+ * });
711
+ *
712
+ * const result = await middleware(request);
713
+ * if (!result.allowed) {
714
+ * throw new Error(result.error);
715
+ * }
716
+ * // Use result.context.actor for the verified actor
717
+ * ```
718
+ */
719
+ export function createVerificationMiddleware(options) {
720
+ const config = createIdentityConfig(options.config);
721
+ return async (request) => {
722
+ const { signedRequest, body } = request;
723
+ const mode = config.mode;
724
+ // Case 1: Soft mode - always allow, extract actor if provided
725
+ if (mode === IdentityMode.SOFT) {
726
+ return {
727
+ allowed: true,
728
+ context: {
729
+ actor: signedRequest?.actor,
730
+ verified: false,
731
+ mode,
732
+ },
733
+ };
734
+ }
735
+ // Case 2: No signed request provided
736
+ if (!signedRequest) {
737
+ if (mode === IdentityMode.CRYPTOGRAPHIC) {
738
+ return {
739
+ allowed: false,
740
+ context: {
741
+ verified: false,
742
+ mode,
743
+ },
744
+ error: 'Signature required in cryptographic mode',
745
+ };
746
+ }
747
+ // Hybrid mode: allow unsigned requests
748
+ return {
749
+ allowed: true,
750
+ context: {
751
+ verified: false,
752
+ mode,
753
+ },
754
+ };
755
+ }
756
+ // Case 3: Signed request provided - verify it
757
+ // Compute request hash
758
+ let requestHash;
759
+ try {
760
+ requestHash = await hashRequestBody(body ?? '');
761
+ }
762
+ catch {
763
+ return {
764
+ allowed: false,
765
+ context: {
766
+ verified: false,
767
+ mode,
768
+ },
769
+ error: 'Failed to compute request hash',
770
+ };
771
+ }
772
+ // Perform verification
773
+ const verificationResult = await verifySignature({
774
+ signedRequest,
775
+ requestHash,
776
+ lookupEntity: options.lookupEntity,
777
+ config,
778
+ now: options.now,
779
+ });
780
+ // Check if request should be allowed
781
+ const allowed = shouldAllowRequest(mode, verificationResult);
782
+ return {
783
+ allowed,
784
+ context: {
785
+ actor: verificationResult.status === VerificationStatus.VALID
786
+ ? verificationResult.actor
787
+ : signedRequest.actor,
788
+ verified: verificationResult.status === VerificationStatus.VALID,
789
+ verificationResult,
790
+ mode,
791
+ },
792
+ error: allowed ? undefined : verificationResult.error ?? 'Verification failed',
793
+ };
794
+ };
795
+ }
796
+ /**
797
+ * Creates middleware context for an unsigned request in soft mode
798
+ */
799
+ export function createSoftModeContext(actor) {
800
+ return {
801
+ actor,
802
+ verified: false,
803
+ mode: IdentityMode.SOFT,
804
+ };
805
+ }
806
+ /**
807
+ * Creates middleware context for a verified request
808
+ */
809
+ export function createVerifiedContext(actor, verificationResult) {
810
+ return {
811
+ actor,
812
+ verified: true,
813
+ verificationResult,
814
+ mode: IdentityMode.CRYPTOGRAPHIC,
815
+ };
816
+ }
817
+ //# sourceMappingURL=identity.js.map