@ambitresearch/genie 1.3.1

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 (301) hide show
  1. package/README.md +15 -0
  2. package/dist/auth/bearer.d.ts +49 -0
  3. package/dist/auth/bearer.d.ts.map +1 -0
  4. package/dist/auth/bearer.js +159 -0
  5. package/dist/auth/bearer.js.map +1 -0
  6. package/dist/auth/oauth/authorize.d.ts +19 -0
  7. package/dist/auth/oauth/authorize.d.ts.map +1 -0
  8. package/dist/auth/oauth/authorize.js +111 -0
  9. package/dist/auth/oauth/authorize.js.map +1 -0
  10. package/dist/auth/oauth/config.d.ts +10 -0
  11. package/dist/auth/oauth/config.d.ts.map +1 -0
  12. package/dist/auth/oauth/config.js +16 -0
  13. package/dist/auth/oauth/config.js.map +1 -0
  14. package/dist/auth/oauth/dcr.d.ts +15 -0
  15. package/dist/auth/oauth/dcr.d.ts.map +1 -0
  16. package/dist/auth/oauth/dcr.js +38 -0
  17. package/dist/auth/oauth/dcr.js.map +1 -0
  18. package/dist/auth/oauth/jwt.d.ts +13 -0
  19. package/dist/auth/oauth/jwt.d.ts.map +1 -0
  20. package/dist/auth/oauth/jwt.js +46 -0
  21. package/dist/auth/oauth/jwt.js.map +1 -0
  22. package/dist/auth/oauth/metadata.d.ts +9 -0
  23. package/dist/auth/oauth/metadata.d.ts.map +1 -0
  24. package/dist/auth/oauth/metadata.js +19 -0
  25. package/dist/auth/oauth/metadata.js.map +1 -0
  26. package/dist/auth/oauth/router.d.ts +32 -0
  27. package/dist/auth/oauth/router.d.ts.map +1 -0
  28. package/dist/auth/oauth/router.js +159 -0
  29. package/dist/auth/oauth/router.js.map +1 -0
  30. package/dist/auth/oauth/store.d.ts +55 -0
  31. package/dist/auth/oauth/store.d.ts.map +1 -0
  32. package/dist/auth/oauth/store.js +63 -0
  33. package/dist/auth/oauth/store.js.map +1 -0
  34. package/dist/auth/oauth/token.d.ts +21 -0
  35. package/dist/auth/oauth/token.d.ts.map +1 -0
  36. package/dist/auth/oauth/token.js +77 -0
  37. package/dist/auth/oauth/token.js.map +1 -0
  38. package/dist/auth/oidc/group-policy.d.ts +43 -0
  39. package/dist/auth/oidc/group-policy.d.ts.map +1 -0
  40. package/dist/auth/oidc/group-policy.js +54 -0
  41. package/dist/auth/oidc/group-policy.js.map +1 -0
  42. package/dist/auth/oidc/verifier.d.ts +42 -0
  43. package/dist/auth/oidc/verifier.d.ts.map +1 -0
  44. package/dist/auth/oidc/verifier.js +145 -0
  45. package/dist/auth/oidc/verifier.js.map +1 -0
  46. package/dist/auth/token-cli.d.ts +9 -0
  47. package/dist/auth/token-cli.d.ts.map +1 -0
  48. package/dist/auth/token-cli.js +98 -0
  49. package/dist/auth/token-cli.js.map +1 -0
  50. package/dist/cli.d.ts +3 -0
  51. package/dist/cli.d.ts.map +1 -0
  52. package/dist/cli.js +149 -0
  53. package/dist/cli.js.map +1 -0
  54. package/dist/config/redact.d.ts +20 -0
  55. package/dist/config/redact.d.ts.map +1 -0
  56. package/dist/config/redact.js +77 -0
  57. package/dist/config/redact.js.map +1 -0
  58. package/dist/config/secrets.d.ts +100 -0
  59. package/dist/config/secrets.d.ts.map +1 -0
  60. package/dist/config/secrets.js +130 -0
  61. package/dist/config/secrets.js.map +1 -0
  62. package/dist/framework/html.d.ts +103 -0
  63. package/dist/framework/html.d.ts.map +1 -0
  64. package/dist/framework/html.js +271 -0
  65. package/dist/framework/html.js.map +1 -0
  66. package/dist/framework/interface.d.ts +130 -0
  67. package/dist/framework/interface.d.ts.map +1 -0
  68. package/dist/framework/interface.js +112 -0
  69. package/dist/framework/interface.js.map +1 -0
  70. package/dist/framework/react.d.ts +133 -0
  71. package/dist/framework/react.d.ts.map +1 -0
  72. package/dist/framework/react.js +243 -0
  73. package/dist/framework/react.js.map +1 -0
  74. package/dist/framework/vue.d.ts +99 -0
  75. package/dist/framework/vue.d.ts.map +1 -0
  76. package/dist/framework/vue.js +375 -0
  77. package/dist/framework/vue.js.map +1 -0
  78. package/dist/index.d.ts +3 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +3 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/llm/client.d.ts +81 -0
  83. package/dist/llm/client.d.ts.map +1 -0
  84. package/dist/llm/client.js +141 -0
  85. package/dist/llm/client.js.map +1 -0
  86. package/dist/llm/component-response.d.ts +111 -0
  87. package/dist/llm/component-response.d.ts.map +1 -0
  88. package/dist/llm/component-response.js +167 -0
  89. package/dist/llm/component-response.js.map +1 -0
  90. package/dist/llm/generated-files.d.ts +10 -0
  91. package/dist/llm/generated-files.d.ts.map +1 -0
  92. package/dist/llm/generated-files.js +26 -0
  93. package/dist/llm/generated-files.js.map +1 -0
  94. package/dist/llm/prompts/CHANGELOG.md +70 -0
  95. package/dist/llm/prompts/generate-component.system.md +99 -0
  96. package/dist/llm/prompts/refine-component.system.md +112 -0
  97. package/dist/llm/prompts.d.ts +66 -0
  98. package/dist/llm/prompts.d.ts.map +1 -0
  99. package/dist/llm/prompts.js +78 -0
  100. package/dist/llm/prompts.js.map +1 -0
  101. package/dist/llm/retry.d.ts +183 -0
  102. package/dist/llm/retry.d.ts.map +1 -0
  103. package/dist/llm/retry.js +475 -0
  104. package/dist/llm/retry.js.map +1 -0
  105. package/dist/llm/schema.d.ts +129 -0
  106. package/dist/llm/schema.d.ts.map +1 -0
  107. package/dist/llm/schema.js +147 -0
  108. package/dist/llm/schema.js.map +1 -0
  109. package/dist/llm/validate.d.ts +107 -0
  110. package/dist/llm/validate.d.ts.map +1 -0
  111. package/dist/llm/validate.js +174 -0
  112. package/dist/llm/validate.js.map +1 -0
  113. package/dist/manifest/compiler.d.ts +112 -0
  114. package/dist/manifest/compiler.d.ts.map +1 -0
  115. package/dist/manifest/compiler.js +306 -0
  116. package/dist/manifest/compiler.js.map +1 -0
  117. package/dist/manifest/ensure.d.ts +46 -0
  118. package/dist/manifest/ensure.d.ts.map +1 -0
  119. package/dist/manifest/ensure.js +49 -0
  120. package/dist/manifest/ensure.js.map +1 -0
  121. package/dist/manifest/index.d.ts +13 -0
  122. package/dist/manifest/index.d.ts.map +1 -0
  123. package/dist/manifest/index.js +13 -0
  124. package/dist/manifest/index.js.map +1 -0
  125. package/dist/middleware/plan-guard.d.ts +167 -0
  126. package/dist/middleware/plan-guard.d.ts.map +1 -0
  127. package/dist/middleware/plan-guard.js +190 -0
  128. package/dist/middleware/plan-guard.js.map +1 -0
  129. package/dist/plans/index.d.ts +71 -0
  130. package/dist/plans/index.d.ts.map +1 -0
  131. package/dist/plans/index.js +252 -0
  132. package/dist/plans/index.js.map +1 -0
  133. package/dist/schemas/component.schema.json +114 -0
  134. package/dist/server.d.ts +96 -0
  135. package/dist/server.d.ts.map +1 -0
  136. package/dist/server.js +258 -0
  137. package/dist/server.js.map +1 -0
  138. package/dist/store/empty-manifest.d.ts +58 -0
  139. package/dist/store/empty-manifest.d.ts.map +1 -0
  140. package/dist/store/empty-manifest.js +59 -0
  141. package/dist/store/empty-manifest.js.map +1 -0
  142. package/dist/store/git-host.d.ts +120 -0
  143. package/dist/store/git-host.d.ts.map +1 -0
  144. package/dist/store/git-host.js +796 -0
  145. package/dist/store/git-host.js.map +1 -0
  146. package/dist/store/index.d.ts +5 -0
  147. package/dist/store/index.d.ts.map +1 -0
  148. package/dist/store/index.js +4 -0
  149. package/dist/store/index.js.map +1 -0
  150. package/dist/store/interface.d.ts +322 -0
  151. package/dist/store/interface.d.ts.map +1 -0
  152. package/dist/store/interface.js +100 -0
  153. package/dist/store/interface.js.map +1 -0
  154. package/dist/store/kit-files.d.ts +88 -0
  155. package/dist/store/kit-files.d.ts.map +1 -0
  156. package/dist/store/kit-files.js +199 -0
  157. package/dist/store/kit-files.js.map +1 -0
  158. package/dist/store/local.d.ts +70 -0
  159. package/dist/store/local.d.ts.map +1 -0
  160. package/dist/store/local.js +651 -0
  161. package/dist/store/local.js.map +1 -0
  162. package/dist/store/manifest.d.ts +44 -0
  163. package/dist/store/manifest.d.ts.map +1 -0
  164. package/dist/store/manifest.js +116 -0
  165. package/dist/store/manifest.js.map +1 -0
  166. package/dist/store/viewer-assets.d.ts +20 -0
  167. package/dist/store/viewer-assets.d.ts.map +1 -0
  168. package/dist/store/viewer-assets.js +118 -0
  169. package/dist/store/viewer-assets.js.map +1 -0
  170. package/dist/sync/anchor.d.ts +125 -0
  171. package/dist/sync/anchor.d.ts.map +1 -0
  172. package/dist/sync/anchor.js +210 -0
  173. package/dist/sync/anchor.js.map +1 -0
  174. package/dist/sync/index.d.ts +15 -0
  175. package/dist/sync/index.d.ts.map +1 -0
  176. package/dist/sync/index.js +15 -0
  177. package/dist/sync/index.js.map +1 -0
  178. package/dist/sync/orchestrator.d.ts +160 -0
  179. package/dist/sync/orchestrator.d.ts.map +1 -0
  180. package/dist/sync/orchestrator.js +288 -0
  181. package/dist/sync/orchestrator.js.map +1 -0
  182. package/dist/tools/bind_kit.d.ts +29 -0
  183. package/dist/tools/bind_kit.d.ts.map +1 -0
  184. package/dist/tools/bind_kit.js +70 -0
  185. package/dist/tools/bind_kit.js.map +1 -0
  186. package/dist/tools/conjure.d.ts +141 -0
  187. package/dist/tools/conjure.d.ts.map +1 -0
  188. package/dist/tools/conjure.js +655 -0
  189. package/dist/tools/conjure.js.map +1 -0
  190. package/dist/tools/conjure_screen.d.ts +174 -0
  191. package/dist/tools/conjure_screen.d.ts.map +1 -0
  192. package/dist/tools/conjure_screen.js +458 -0
  193. package/dist/tools/conjure_screen.js.map +1 -0
  194. package/dist/tools/create_kit.d.ts +32 -0
  195. package/dist/tools/create_kit.d.ts.map +1 -0
  196. package/dist/tools/create_kit.js +131 -0
  197. package/dist/tools/create_kit.js.map +1 -0
  198. package/dist/tools/create_project.d.ts +267 -0
  199. package/dist/tools/create_project.d.ts.map +1 -0
  200. package/dist/tools/create_project.js +546 -0
  201. package/dist/tools/create_project.js.map +1 -0
  202. package/dist/tools/delete_files.d.ts +93 -0
  203. package/dist/tools/delete_files.d.ts.map +1 -0
  204. package/dist/tools/delete_files.js +276 -0
  205. package/dist/tools/delete_files.js.map +1 -0
  206. package/dist/tools/delete_project.d.ts +43 -0
  207. package/dist/tools/delete_project.d.ts.map +1 -0
  208. package/dist/tools/delete_project.js +118 -0
  209. package/dist/tools/delete_project.js.map +1 -0
  210. package/dist/tools/get_kit.d.ts +28 -0
  211. package/dist/tools/get_kit.d.ts.map +1 -0
  212. package/dist/tools/get_kit.js +95 -0
  213. package/dist/tools/get_kit.js.map +1 -0
  214. package/dist/tools/get_project.d.ts +37 -0
  215. package/dist/tools/get_project.d.ts.map +1 -0
  216. package/dist/tools/get_project.js +71 -0
  217. package/dist/tools/get_project.js.map +1 -0
  218. package/dist/tools/list_components.d.ts +42 -0
  219. package/dist/tools/list_components.d.ts.map +1 -0
  220. package/dist/tools/list_components.js +125 -0
  221. package/dist/tools/list_components.js.map +1 -0
  222. package/dist/tools/list_files.d.ts +32 -0
  223. package/dist/tools/list_files.d.ts.map +1 -0
  224. package/dist/tools/list_files.js +109 -0
  225. package/dist/tools/list_files.js.map +1 -0
  226. package/dist/tools/list_kits.d.ts +14 -0
  227. package/dist/tools/list_kits.d.ts.map +1 -0
  228. package/dist/tools/list_kits.js +41 -0
  229. package/dist/tools/list_kits.js.map +1 -0
  230. package/dist/tools/list_projects.d.ts +23 -0
  231. package/dist/tools/list_projects.d.ts.map +1 -0
  232. package/dist/tools/list_projects.js +66 -0
  233. package/dist/tools/list_projects.js.map +1 -0
  234. package/dist/tools/plan.d.ts +16 -0
  235. package/dist/tools/plan.d.ts.map +1 -0
  236. package/dist/tools/plan.js +146 -0
  237. package/dist/tools/plan.js.map +1 -0
  238. package/dist/tools/preview.d.ts +289 -0
  239. package/dist/tools/preview.d.ts.map +1 -0
  240. package/dist/tools/preview.js +672 -0
  241. package/dist/tools/preview.js.map +1 -0
  242. package/dist/tools/read_file.d.ts +40 -0
  243. package/dist/tools/read_file.d.ts.map +1 -0
  244. package/dist/tools/read_file.js +114 -0
  245. package/dist/tools/read_file.js.map +1 -0
  246. package/dist/tools/refine.d.ts +220 -0
  247. package/dist/tools/refine.d.ts.map +1 -0
  248. package/dist/tools/refine.js +501 -0
  249. package/dist/tools/refine.js.map +1 -0
  250. package/dist/tools/validate.d.ts +74 -0
  251. package/dist/tools/validate.d.ts.map +1 -0
  252. package/dist/tools/validate.js +224 -0
  253. package/dist/tools/validate.js.map +1 -0
  254. package/dist/tools/write_files.d.ts +158 -0
  255. package/dist/tools/write_files.d.ts.map +1 -0
  256. package/dist/tools/write_files.js +541 -0
  257. package/dist/tools/write_files.js.map +1 -0
  258. package/dist/transport.d.ts +77 -0
  259. package/dist/transport.d.ts.map +1 -0
  260. package/dist/transport.js +448 -0
  261. package/dist/transport.js.map +1 -0
  262. package/dist/ui/card-asset-broker.d.ts +42 -0
  263. package/dist/ui/card-asset-broker.d.ts.map +1 -0
  264. package/dist/ui/card-asset-broker.js +479 -0
  265. package/dist/ui/card-asset-broker.js.map +1 -0
  266. package/dist/ui/grid-resource.d.ts +252 -0
  267. package/dist/ui/grid-resource.d.ts.map +1 -0
  268. package/dist/ui/grid-resource.js +680 -0
  269. package/dist/ui/grid-resource.js.map +1 -0
  270. package/dist/ui/viewer-static/index.html +95 -0
  271. package/dist/ui/viewer-static/viewer.css +451 -0
  272. package/dist/ui/viewer-static/viewer.js +1405 -0
  273. package/dist/validate/full-scan.d.ts +87 -0
  274. package/dist/validate/full-scan.d.ts.map +1 -0
  275. package/dist/validate/full-scan.js +299 -0
  276. package/dist/validate/full-scan.js.map +1 -0
  277. package/dist/validate/index.d.ts +15 -0
  278. package/dist/validate/index.d.ts.map +1 -0
  279. package/dist/validate/index.js +18 -0
  280. package/dist/validate/index.js.map +1 -0
  281. package/dist/validate/marker.d.ts +89 -0
  282. package/dist/validate/marker.d.ts.map +1 -0
  283. package/dist/validate/marker.js +87 -0
  284. package/dist/validate/marker.js.map +1 -0
  285. package/dist/validate/phash.d.ts +135 -0
  286. package/dist/validate/phash.d.ts.map +1 -0
  287. package/dist/validate/phash.js +307 -0
  288. package/dist/validate/phash.js.map +1 -0
  289. package/dist/validate/render.d.ts +38 -0
  290. package/dist/validate/render.d.ts.map +1 -0
  291. package/dist/validate/render.js +155 -0
  292. package/dist/validate/render.js.map +1 -0
  293. package/dist/watch/index.d.ts +12 -0
  294. package/dist/watch/index.d.ts.map +1 -0
  295. package/dist/watch/index.js +12 -0
  296. package/dist/watch/index.js.map +1 -0
  297. package/dist/watch/watcher.d.ts +65 -0
  298. package/dist/watch/watcher.d.ts.map +1 -0
  299. package/dist/watch/watcher.js +238 -0
  300. package/dist/watch/watcher.js.map +1 -0
  301. package/package.json +70 -0
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @ambitresearch/genie
2
+
3
+ The genie MCP server provides harness-agnostic UI-component generation, refinement,
4
+ validation, and preview tools. See the [genie documentation](https://ambitresearch.github.io/genie/)
5
+ for installation, configuration, and harness-specific setup.
6
+
7
+ ## Release verification
8
+
9
+ Releases include npm provenance plus signed tarballs, SBOMs, container images, and the
10
+ Claude Desktop bundle. After installing the package, run `npm audit signatures` to check
11
+ its registry signature and provenance. For release downloads, use `cosign verify-blob`
12
+ with the adjacent `.sig` bundle.
13
+
14
+ The [supply-chain verification guide](https://github.com/ambitresearch/genie/blob/main/docs/supply-chain.md#verifying-a-release)
15
+ contains the complete certificate identity, issuer, and container verification commands.
@@ -0,0 +1,49 @@
1
+ export type TokenScope = "read" | "write";
2
+ /** A single stored token record — hash only, never the plaintext. */
3
+ export interface StoredToken {
4
+ /** SHA-256 hex digest of the plaintext token. Acts as the record's key. */
5
+ hash: string;
6
+ /** First 12 chars of the plaintext (`genie_xxxxxxxx`) — safe to display/log. */
7
+ prefix: string;
8
+ sub: string;
9
+ scopes: TokenScope[];
10
+ createdAt: string;
11
+ lastUsedAt: string | null;
12
+ }
13
+ /** Generate a new plaintext token: `genie_<32-char-base32>`. */
14
+ export declare function generateToken(): string;
15
+ /** True if `token` matches the expected `genie_<32-char-base32>` shape. */
16
+ export declare function isValidTokenFormat(token: string): boolean;
17
+ export interface CreateTokenOptions {
18
+ sub: string;
19
+ scopes?: TokenScope[];
20
+ }
21
+ /**
22
+ * Mint a new token, persist its hash, and return the plaintext.
23
+ * The plaintext is returned exactly once — callers (the CLI) must print it
24
+ * and never log/store it themselves.
25
+ */
26
+ export declare function createToken(options: CreateTokenOptions): Promise<{
27
+ token: string;
28
+ record: StoredToken;
29
+ }>;
30
+ /** List all stored token records (hashes only — never plaintext). */
31
+ export declare function listTokens(): Promise<StoredToken[]>;
32
+ /**
33
+ * Revoke every stored token whose prefix starts with `prefix`. Returns the
34
+ * number of tokens removed.
35
+ */
36
+ export declare function revokeToken(prefix: string): Promise<number>;
37
+ export interface TokenVerificationResult {
38
+ ok: boolean;
39
+ record?: StoredToken;
40
+ }
41
+ /**
42
+ * Verify a presented plaintext token against the store. Constant-time per
43
+ * candidate hash comparison (AC: "constant-time crypto.timingSafeEqual").
44
+ * On success, updates `lastUsedAt` for that record (AC4).
45
+ */
46
+ export declare function verifyToken(token: string): Promise<TokenVerificationResult>;
47
+ /** Extract a bearer token from an `Authorization` header value, if present. */
48
+ export declare function extractBearerToken(authorizationHeader: string | undefined): string | undefined;
49
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/auth/bearer.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1C,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA6CD,gEAAgE;AAChE,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAoBD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC,CAgBD;AAED,qEAAqE;AACrE,wBAAsB,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAGzD;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOjE;AAUD,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgBjF;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI9F"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Static Bearer token authentication (M5-02, DRO-274).
3
+ *
4
+ * Covers harnesses that don't implement OAuth Dynamic Client Registration
5
+ * (VS Code Copilot, Cline, Continue.dev): they attach a static
6
+ * `Authorization: Bearer <token>` header on every MCP-over-HTTP request.
7
+ * Tokens are minted out-of-band via the admin CLI (`genie token create`),
8
+ * never re-derived from a password, and stored **hashed** (SHA-256) at
9
+ * `${GENIE_HOME}/auth/tokens.json` so the plaintext token exists only at
10
+ * mint time (shown once) and in the client's own header.
11
+ *
12
+ * Token format: `genie_<32-char-base32>` — deliberately similar to a GitHub
13
+ * PAT (`ghp_...`) so it visually reads as "a secret, handle carefully" and
14
+ * is easy to grep/rotate/revoke by prefix.
15
+ */
16
+ import { randomBytes, createHash, timingSafeEqual } from "node:crypto";
17
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
18
+ import { resolve } from "node:path";
19
+ const TOKEN_PREFIX = "genie_";
20
+ /** Crockford-ish base32 alphabet (no padding, unambiguous glyphs). */
21
+ const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
22
+ const BASE32_BODY_LENGTH = 32;
23
+ const TOKEN_PATTERN = new RegExp(`^${TOKEN_PREFIX}[${BASE32_ALPHABET}]{${BASE32_BODY_LENGTH}}$`);
24
+ /** Get the auth directory path from GENIE_HOME. */
25
+ function getAuthDir() {
26
+ const home = process.env.GENIE_HOME || resolve(process.cwd(), ".genie");
27
+ return resolve(home, "auth");
28
+ }
29
+ function getTokensPath() {
30
+ return resolve(getAuthDir(), "tokens.json");
31
+ }
32
+ function sha256Hex(input) {
33
+ return createHash("sha256").update(input, "utf8").digest("hex");
34
+ }
35
+ /** Encode raw bytes as unpadded base32 using BASE32_ALPHABET. */
36
+ function base32Encode(buf, length) {
37
+ let bits = 0;
38
+ let value = 0;
39
+ let out = "";
40
+ for (const byte of buf) {
41
+ value = (value << 8) | byte;
42
+ bits += 8;
43
+ while (bits >= 5) {
44
+ out += BASE32_ALPHABET[(value >>> (bits - 5)) & 0x1f];
45
+ bits -= 5;
46
+ }
47
+ }
48
+ if (bits > 0) {
49
+ out += BASE32_ALPHABET[(value << (5 - bits)) & 0x1f];
50
+ }
51
+ return out.slice(0, length);
52
+ }
53
+ /** Generate a new plaintext token: `genie_<32-char-base32>`. */
54
+ export function generateToken() {
55
+ // 32 base32 chars needs ceil(32*5/8) = 20 bytes of entropy.
56
+ const body = base32Encode(randomBytes(20), BASE32_BODY_LENGTH);
57
+ return `${TOKEN_PREFIX}${body}`;
58
+ }
59
+ /** True if `token` matches the expected `genie_<32-char-base32>` shape. */
60
+ export function isValidTokenFormat(token) {
61
+ return TOKEN_PATTERN.test(token);
62
+ }
63
+ /** Read the token store from disk. Returns an empty store if missing/corrupt. */
64
+ async function loadStore() {
65
+ try {
66
+ const content = await readFile(getTokensPath(), "utf-8");
67
+ const parsed = JSON.parse(content);
68
+ if (!Array.isArray(parsed.tokens))
69
+ return { tokens: [] };
70
+ return parsed;
71
+ }
72
+ catch {
73
+ return { tokens: [] };
74
+ }
75
+ }
76
+ async function saveStore(store) {
77
+ const dir = getAuthDir();
78
+ await mkdir(dir, { recursive: true, mode: 0o700 });
79
+ await writeFile(getTokensPath(), JSON.stringify(store, null, 2), { mode: 0o600 });
80
+ }
81
+ /**
82
+ * Mint a new token, persist its hash, and return the plaintext.
83
+ * The plaintext is returned exactly once — callers (the CLI) must print it
84
+ * and never log/store it themselves.
85
+ */
86
+ export async function createToken(options) {
87
+ const scopes = options.scopes && options.scopes.length > 0 ? options.scopes : ["read"];
88
+ const token = generateToken();
89
+ const record = {
90
+ hash: sha256Hex(token),
91
+ prefix: token.slice(0, 12),
92
+ sub: options.sub,
93
+ scopes,
94
+ createdAt: new Date().toISOString(),
95
+ lastUsedAt: null,
96
+ };
97
+ const store = await loadStore();
98
+ store.tokens.push(record);
99
+ await saveStore(store);
100
+ return { token, record };
101
+ }
102
+ /** List all stored token records (hashes only — never plaintext). */
103
+ export async function listTokens() {
104
+ const store = await loadStore();
105
+ return store.tokens;
106
+ }
107
+ /**
108
+ * Revoke every stored token whose prefix starts with `prefix`. Returns the
109
+ * number of tokens removed.
110
+ */
111
+ export async function revokeToken(prefix) {
112
+ const store = await loadStore();
113
+ const before = store.tokens.length;
114
+ store.tokens = store.tokens.filter((t) => !t.prefix.startsWith(prefix));
115
+ const removed = before - store.tokens.length;
116
+ if (removed > 0)
117
+ await saveStore(store);
118
+ return removed;
119
+ }
120
+ /** Constant-time compare of two hex digests (equal length required). */
121
+ function hexTimingSafeEqual(a, b) {
122
+ const bufA = Buffer.from(a, "hex");
123
+ const bufB = Buffer.from(b, "hex");
124
+ if (bufA.length !== bufB.length)
125
+ return false;
126
+ return timingSafeEqual(bufA, bufB);
127
+ }
128
+ /**
129
+ * Verify a presented plaintext token against the store. Constant-time per
130
+ * candidate hash comparison (AC: "constant-time crypto.timingSafeEqual").
131
+ * On success, updates `lastUsedAt` for that record (AC4).
132
+ */
133
+ export async function verifyToken(token) {
134
+ if (!isValidTokenFormat(token))
135
+ return { ok: false };
136
+ const candidateHash = sha256Hex(token);
137
+ const store = await loadStore();
138
+ let matched;
139
+ // Iterate every record rather than short-circuiting on a Map lookup, so
140
+ // comparison time doesn't leak whether *some* token existed vs none.
141
+ for (const record of store.tokens) {
142
+ if (hexTimingSafeEqual(record.hash, candidateHash)) {
143
+ matched = record;
144
+ }
145
+ }
146
+ if (matched === undefined)
147
+ return { ok: false };
148
+ matched.lastUsedAt = new Date().toISOString();
149
+ await saveStore(store);
150
+ return { ok: true, record: matched };
151
+ }
152
+ /** Extract a bearer token from an `Authorization` header value, if present. */
153
+ export function extractBearerToken(authorizationHeader) {
154
+ if (!authorizationHeader)
155
+ return undefined;
156
+ const match = /^Bearer\s+(\S+)$/i.exec(authorizationHeader.trim());
157
+ return match?.[1];
158
+ }
159
+ //# sourceMappingURL=bearer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../src/auth/bearer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAC9B,sEAAsE;AACtE,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAC3D,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,IAAI,eAAe,KAAK,kBAAkB,IAAI,CAAC,CAAC;AAEjG,mDAAmD;AACnD,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IACxE,OAAO,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED,iEAAiE;AACjE,SAAS,YAAY,CAAC,GAAW,EAAE,MAAc;IAC/C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;QACvB,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QAC5B,IAAI,IAAI,CAAC,CAAC;QACV,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC;YACjB,GAAG,IAAI,eAAe,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YACtD,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACb,GAAG,IAAI,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa;IAC3B,4DAA4D;IAC5D,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC/D,OAAO,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,SAAS;IACtB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QACzD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAAqB;IAC5C,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,MAAM,SAAS,CAAC,aAAa,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACpF,CAAC;AAOD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAA2B;IAI3D,MAAM,MAAM,GACV,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,MAAM,CAAkB,CAAC;IAC5F,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAgB;QAC1B,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC;QACtB,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QAC1B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM;QACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,UAAU,EAAE,IAAI;KACjB,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc;IAC9C,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IAC7C,IAAI,OAAO,GAAG,CAAC;QAAE,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wEAAwE;AACxE,SAAS,kBAAkB,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAOD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAa;IAC7C,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACrD,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,IAAI,OAAgC,CAAC;IACrC,wEAAwE;IACxE,qEAAqE;IACrE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,kBAAkB,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;YACnD,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAChD,OAAO,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAAC,mBAAuC;IACxE,IAAI,CAAC,mBAAmB;QAAE,OAAO,SAAS,CAAC;IAC3C,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { OAuthStore } from "./store.js";
2
+ export interface AuthorizeQuery {
3
+ response_type?: string;
4
+ client_id?: string;
5
+ redirect_uri?: string;
6
+ scope?: string;
7
+ state?: string;
8
+ code_challenge?: string;
9
+ code_challenge_method?: string;
10
+ }
11
+ export declare class AuthorizeError extends Error {
12
+ readonly status: number;
13
+ constructor(message: string, status?: number);
14
+ }
15
+ /** AC3 — render the minimal server-rendered consent screen (GET /authorize). */
16
+ export declare function renderConsentScreen(store: OAuthStore, query: AuthorizeQuery): string;
17
+ /** AC3 — handle the consent decision (POST /authorize); returns the redirect URL. */
18
+ export declare function handleConsentDecision(store: OAuthStore, params: URLSearchParams, decision: string): string;
19
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,qBAAa,cAAe,SAAQ,KAAK;IAGrC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACN,MAAM,GAAE,MAAY;CAIhC;AAmDD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CA4BpF;AAED,qFAAqF;AACrF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,MAAM,GACf,MAAM,CA8BR"}
@@ -0,0 +1,111 @@
1
+ export class AuthorizeError extends Error {
2
+ status;
3
+ constructor(message, status = 400) {
4
+ super(message);
5
+ this.status = status;
6
+ }
7
+ }
8
+ const SUPPORTED_SCOPES = ["read", "write"];
9
+ function escapeHtml(value) {
10
+ return value.replace(/[&<>"']/g, (ch) => {
11
+ switch (ch) {
12
+ case "&":
13
+ return "&amp;";
14
+ case "<":
15
+ return "&lt;";
16
+ case ">":
17
+ return "&gt;";
18
+ case '"':
19
+ return "&quot;";
20
+ default:
21
+ return "&#39;";
22
+ }
23
+ });
24
+ }
25
+ function validateAuthorizeRequest(store, query) {
26
+ if (query.response_type !== "code") {
27
+ throw new AuthorizeError("response_type must be 'code'.");
28
+ }
29
+ if (!query.client_id)
30
+ throw new AuthorizeError("client_id is required.");
31
+ const client = store.getClient(query.client_id);
32
+ if (client === undefined)
33
+ throw new AuthorizeError("Unknown client_id.");
34
+ if (!query.redirect_uri || !client.redirect_uris.includes(query.redirect_uri)) {
35
+ throw new AuthorizeError("redirect_uri is missing or not registered for this client.");
36
+ }
37
+ if (query.code_challenge_method !== "S256" || !query.code_challenge) {
38
+ throw new AuthorizeError("PKCE code_challenge with method S256 is required.");
39
+ }
40
+ const requestedScopes = (query.scope ?? "read").split(/\s+/).filter(Boolean);
41
+ const scopes = requestedScopes.filter((s) => SUPPORTED_SCOPES.includes(s));
42
+ if (scopes.length === 0)
43
+ throw new AuthorizeError("No valid scopes requested.");
44
+ return {
45
+ clientId: query.client_id,
46
+ redirectUri: query.redirect_uri,
47
+ scopes,
48
+ codeChallenge: query.code_challenge,
49
+ };
50
+ }
51
+ /** AC3 — render the minimal server-rendered consent screen (GET /authorize). */
52
+ export function renderConsentScreen(store, query) {
53
+ const { scopes } = validateAuthorizeRequest(store, query);
54
+ const params = new URLSearchParams({
55
+ response_type: query.response_type ?? "",
56
+ client_id: query.client_id ?? "",
57
+ redirect_uri: query.redirect_uri ?? "",
58
+ scope: scopes.join(" "),
59
+ state: query.state ?? "",
60
+ code_challenge: query.code_challenge ?? "",
61
+ code_challenge_method: query.code_challenge_method ?? "",
62
+ });
63
+ return `<!doctype html>
64
+ <html>
65
+ <head><meta charset="utf-8"><title>genie — Authorize</title></head>
66
+ <body>
67
+ <h1>Authorize access to genie</h1>
68
+ <p>This application is requesting the following permissions:</p>
69
+ <ul>
70
+ ${scopes.map((s) => `<li>${escapeHtml(s)}</li>`).join("\n ")}
71
+ </ul>
72
+ <form method="POST" action="/authorize">
73
+ <input type="hidden" name="params" value="${escapeHtml(params.toString())}">
74
+ <button type="submit" name="decision" value="allow">Allow</button>
75
+ <button type="submit" name="decision" value="deny">Deny</button>
76
+ </form>
77
+ </body>
78
+ </html>`;
79
+ }
80
+ /** AC3 — handle the consent decision (POST /authorize); returns the redirect URL. */
81
+ export function handleConsentDecision(store, params, decision) {
82
+ const query = {
83
+ response_type: params.get("response_type") ?? undefined,
84
+ client_id: params.get("client_id") ?? undefined,
85
+ redirect_uri: params.get("redirect_uri") ?? undefined,
86
+ scope: params.get("scope") ?? undefined,
87
+ state: params.get("state") ?? undefined,
88
+ code_challenge: params.get("code_challenge") ?? undefined,
89
+ code_challenge_method: params.get("code_challenge_method") ?? undefined,
90
+ };
91
+ const { clientId, redirectUri, scopes, codeChallenge } = validateAuthorizeRequest(store, query);
92
+ const redirectUrl = new URL(redirectUri);
93
+ if (decision !== "allow") {
94
+ redirectUrl.searchParams.set("error", "access_denied");
95
+ if (query.state)
96
+ redirectUrl.searchParams.set("state", query.state);
97
+ return redirectUrl.toString();
98
+ }
99
+ const code = store.issueAuthorizationCode({
100
+ client_id: clientId,
101
+ redirect_uri: redirectUri,
102
+ scope: scopes.join(" "),
103
+ code_challenge: codeChallenge,
104
+ code_challenge_method: "S256",
105
+ });
106
+ redirectUrl.searchParams.set("code", code);
107
+ if (query.state)
108
+ redirectUrl.searchParams.set("state", query.state);
109
+ return redirectUrl.toString();
110
+ }
111
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../../src/auth/oauth/authorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OAuth signing-key resolution (DRO-273 / M5-01).
3
+ *
4
+ * The server refuses to start OAuth-protected endpoints without a signing
5
+ * key of adequate length — see Implementation Notes in the issue.
6
+ */
7
+ export declare class MissingOAuthKeyError extends Error {
8
+ }
9
+ export declare function resolveOAuthSigningKey(env?: NodeJS.ProcessEnv): string;
10
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAElD,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQnF"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * OAuth signing-key resolution (DRO-273 / M5-01).
3
+ *
4
+ * The server refuses to start OAuth-protected endpoints without a signing
5
+ * key of adequate length — see Implementation Notes in the issue.
6
+ */
7
+ export class MissingOAuthKeyError extends Error {
8
+ }
9
+ export function resolveOAuthSigningKey(env = process.env) {
10
+ const key = env.OAUTH_HS256_KEY;
11
+ if (key === undefined || key.length < 32) {
12
+ throw new MissingOAuthKeyError("OAUTH_HS256_KEY env var must be set to a string of at least 32 characters to enable OAuth.");
13
+ }
14
+ return key;
15
+ }
16
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/auth/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAElD,MAAM,UAAU,sBAAsB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACzE,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC;IAChC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,oBAAoB,CAC5B,4FAA4F,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { OAuthStore } from "./store.js";
2
+ export interface DcrRequestBody {
3
+ redirect_uris?: unknown;
4
+ client_name?: unknown;
5
+ [key: string]: unknown;
6
+ }
7
+ export declare class DcrValidationError extends Error {
8
+ }
9
+ /**
10
+ * RFC 7591 Dynamic Client Registration (AC2). Accepts a DCR request body and
11
+ * registers a new client, returning `{ client_id, client_secret,
12
+ * client_secret_expires_at: 0 }` (non-expiring secret — no rotation in v1).
13
+ */
14
+ export declare function registerDynamicClient(store: OAuthStore, body: DcrRequestBody): Record<string, unknown>;
15
+ //# sourceMappingURL=dcr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dcr.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/dcr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAEhD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,cAAc,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BzB"}
@@ -0,0 +1,38 @@
1
+ export class DcrValidationError extends Error {
2
+ }
3
+ /**
4
+ * RFC 7591 Dynamic Client Registration (AC2). Accepts a DCR request body and
5
+ * registers a new client, returning `{ client_id, client_secret,
6
+ * client_secret_expires_at: 0 }` (non-expiring secret — no rotation in v1).
7
+ */
8
+ export function registerDynamicClient(store, body) {
9
+ const redirectUris = body.redirect_uris;
10
+ if (!Array.isArray(redirectUris) || redirectUris.length === 0) {
11
+ throw new DcrValidationError("redirect_uris is required and must be a non-empty array.");
12
+ }
13
+ for (const uri of redirectUris) {
14
+ if (typeof uri !== "string" || uri.length === 0) {
15
+ throw new DcrValidationError("Each redirect_uri must be a non-empty string.");
16
+ }
17
+ let parsedUri;
18
+ try {
19
+ parsedUri = new URL(uri);
20
+ }
21
+ catch {
22
+ throw new DcrValidationError("Each redirect_uri must be an absolute HTTP(S) URL.");
23
+ }
24
+ if (parsedUri.protocol !== "http:" && parsedUri.protocol !== "https:") {
25
+ throw new DcrValidationError("Each redirect_uri must use the http or https scheme.");
26
+ }
27
+ }
28
+ const clientName = typeof body.client_name === "string" ? body.client_name : undefined;
29
+ const client = store.registerClient(redirectUris, clientName);
30
+ return {
31
+ client_id: client.client_id,
32
+ client_secret: client.client_secret,
33
+ client_secret_expires_at: client.client_secret_expires_at,
34
+ redirect_uris: client.redirect_uris,
35
+ client_name: client.client_name,
36
+ };
37
+ }
38
+ //# sourceMappingURL=dcr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dcr.js","sourceRoot":"","sources":["../../../src/auth/oauth/dcr.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;CAAG;AAEhD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAiB,EACjB,IAAoB;IAEpB,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,kBAAkB,CAAC,0DAA0D,CAAC,CAAC;IAC3F,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,kBAAkB,CAAC,+CAA+C,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,SAAc,CAAC;QACnB,IAAI,CAAC;YACH,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,kBAAkB,CAAC,oDAAoD,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,SAAS,CAAC,QAAQ,KAAK,OAAO,IAAI,SAAS,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACtE,MAAM,IAAI,kBAAkB,CAAC,sDAAsD,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvF,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,CAAC,YAAwB,EAAE,UAAU,CAAC,CAAC;IAC1E,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,wBAAwB,EAAE,MAAM,CAAC,wBAAwB;QACzD,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,WAAW,EAAE,MAAM,CAAC,WAAW;KAChC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface JwtPayload {
2
+ sub: string;
3
+ scope: string;
4
+ iat: number;
5
+ exp: number;
6
+ client_id: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export declare function signJwtHS256(payload: JwtPayload, secret: string): string;
10
+ export declare class JwtVerificationError extends Error {
11
+ }
12
+ export declare function verifyJwtHS256(token: string, secret: string): JwtPayload;
13
+ //# sourceMappingURL=jwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/jwt.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOxE;AAED,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAElD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAsBxE"}
@@ -0,0 +1,46 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ /** Minimal HS256 JWT sign/verify — no external deps (RFC 7519 subset used by genie's OAuth). */
3
+ function base64url(input) {
4
+ const buf = typeof input === "string" ? Buffer.from(input, "utf8") : input;
5
+ return buf.toString("base64url");
6
+ }
7
+ function base64urlToBuffer(input) {
8
+ return Buffer.from(input, "base64url");
9
+ }
10
+ export function signJwtHS256(payload, secret) {
11
+ const header = { alg: "HS256", typ: "JWT" };
12
+ const encodedHeader = base64url(JSON.stringify(header));
13
+ const encodedPayload = base64url(JSON.stringify(payload));
14
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
15
+ const signature = createHmac("sha256", secret).update(signingInput).digest();
16
+ return `${signingInput}.${base64url(signature)}`;
17
+ }
18
+ export class JwtVerificationError extends Error {
19
+ }
20
+ export function verifyJwtHS256(token, secret) {
21
+ const parts = token.split(".");
22
+ if (parts.length !== 3)
23
+ throw new JwtVerificationError("Malformed JWT");
24
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
25
+ let header;
26
+ try {
27
+ header = JSON.parse(base64urlToBuffer(encodedHeader).toString("utf8"));
28
+ }
29
+ catch {
30
+ throw new JwtVerificationError("Malformed JWT header");
31
+ }
32
+ if (header.alg !== "HS256")
33
+ throw new JwtVerificationError("Unsupported JWT algorithm");
34
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
35
+ const expected = createHmac("sha256", secret).update(signingInput).digest();
36
+ const actual = base64urlToBuffer(encodedSignature);
37
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
38
+ throw new JwtVerificationError("Invalid signature");
39
+ }
40
+ const payload = JSON.parse(base64urlToBuffer(encodedPayload).toString("utf8"));
41
+ if (typeof payload.exp === "number" && Date.now() / 1000 > payload.exp) {
42
+ throw new JwtVerificationError("Token expired");
43
+ }
44
+ return payload;
45
+ }
46
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../../src/auth/oauth/jwt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface OAuthMetadataOptions {
2
+ issuer: string;
3
+ }
4
+ /**
5
+ * RFC 8414 Authorization Server Metadata (AC1). Served at
6
+ * `/.well-known/oauth-authorization-server`.
7
+ */
8
+ export declare function buildAuthorizationServerMetadata(opts: OAuthMetadataOptions): Record<string, unknown>;
9
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/metadata.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAapG"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * RFC 8414 Authorization Server Metadata (AC1). Served at
3
+ * `/.well-known/oauth-authorization-server`.
4
+ */
5
+ export function buildAuthorizationServerMetadata(opts) {
6
+ const { issuer } = opts;
7
+ return {
8
+ issuer,
9
+ authorization_endpoint: `${issuer}/authorize`,
10
+ token_endpoint: `${issuer}/token`,
11
+ registration_endpoint: `${issuer}/register`,
12
+ scopes_supported: ["read", "write"],
13
+ response_types_supported: ["code"],
14
+ grant_types_supported: ["authorization_code", "refresh_token"],
15
+ code_challenge_methods_supported: ["S256"],
16
+ token_endpoint_auth_methods_supported: ["client_secret_post", "none"],
17
+ };
18
+ }
19
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../src/auth/oauth/metadata.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,UAAU,gCAAgC,CAAC,IAA0B;IACzE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACxB,OAAO;QACL,MAAM;QACN,sBAAsB,EAAE,GAAG,MAAM,YAAY;QAC7C,cAAc,EAAE,GAAG,MAAM,QAAQ;QACjC,qBAAqB,EAAE,GAAG,MAAM,WAAW;QAC3C,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;QACnC,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,qBAAqB,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;QAC9D,gCAAgC,EAAE,CAAC,MAAM,CAAC;QAC1C,qCAAqC,EAAE,CAAC,oBAAoB,EAAE,MAAM,CAAC;KACtE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { OAuthStore } from "./store.js";
3
+ import { type JwtPayload } from "./jwt.js";
4
+ import { MissingOAuthKeyError } from "./config.js";
5
+ export { MissingOAuthKeyError };
6
+ export interface OAuthHttpRouterOptions {
7
+ /** Public base URL clients see this server at (issuer). */
8
+ issuer: string;
9
+ env?: NodeJS.ProcessEnv;
10
+ }
11
+ /**
12
+ * Wires the OAuth 2.0 + DCR endpoints (RFC 7591 / RFC 8414) onto genie's
13
+ * Streamable HTTP server (DRO-273 / M5-01):
14
+ * GET /.well-known/oauth-authorization-server
15
+ * POST /register
16
+ * GET /authorize (consent screen)
17
+ * POST /authorize (consent decision)
18
+ * POST /token
19
+ *
20
+ * Returns `undefined` (feature disabled) if `OAUTH_HS256_KEY` is unset or too
21
+ * short — callers should refuse to start OAuth-protected routes in that case
22
+ * per the issue's Implementation Notes, while still allowing unauthenticated
23
+ * MCP transport to run (OAuth is opt-in).
24
+ */
25
+ export declare function createOAuthRouter(opts: OAuthHttpRouterOptions): {
26
+ signingKey: string;
27
+ store: OAuthStore;
28
+ handle: (req: IncomingMessage, res: ServerResponse, pathname: string) => boolean;
29
+ verifyBearerToken: (authorizationHeader: string | undefined) => JwtPayload;
30
+ };
31
+ export declare function tryCreateOAuthRouter(opts: OAuthHttpRouterOptions): ReturnType<typeof createOAuthRouter> | undefined;
32
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKxC,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAA0B,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,sBAAsB,GAAG;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACjF,iBAAiB,EAAE,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,KAAK,UAAU,CAAC;CAC5E,CAiIA;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,sBAAsB,GAC3B,UAAU,CAAC,OAAO,iBAAiB,CAAC,GAAG,SAAS,CAOlD"}