@code-rag/core 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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/auth/audit-log.d.ts +35 -0
  4. package/dist/auth/audit-log.js +110 -0
  5. package/dist/auth/audit-log.js.map +1 -0
  6. package/dist/auth/audit-log.test.d.ts +1 -0
  7. package/dist/auth/audit-log.test.js +261 -0
  8. package/dist/auth/audit-log.test.js.map +1 -0
  9. package/dist/auth/index.d.ts +6 -0
  10. package/dist/auth/index.js +5 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/auth/oidc-provider.d.ts +49 -0
  13. package/dist/auth/oidc-provider.js +358 -0
  14. package/dist/auth/oidc-provider.js.map +1 -0
  15. package/dist/auth/oidc-provider.test.d.ts +1 -0
  16. package/dist/auth/oidc-provider.test.js +520 -0
  17. package/dist/auth/oidc-provider.test.js.map +1 -0
  18. package/dist/auth/rbac.d.ts +29 -0
  19. package/dist/auth/rbac.js +75 -0
  20. package/dist/auth/rbac.js.map +1 -0
  21. package/dist/auth/rbac.test.d.ts +1 -0
  22. package/dist/auth/rbac.test.js +224 -0
  23. package/dist/auth/rbac.test.js.map +1 -0
  24. package/dist/auth/saml-provider.d.ts +51 -0
  25. package/dist/auth/saml-provider.js +355 -0
  26. package/dist/auth/saml-provider.js.map +1 -0
  27. package/dist/auth/saml-provider.test.d.ts +1 -0
  28. package/dist/auth/saml-provider.test.js +422 -0
  29. package/dist/auth/saml-provider.test.js.map +1 -0
  30. package/dist/auth/types.d.ts +81 -0
  31. package/dist/auth/types.js +11 -0
  32. package/dist/auth/types.js.map +1 -0
  33. package/dist/auth/types.test.d.ts +1 -0
  34. package/dist/auth/types.test.js +147 -0
  35. package/dist/auth/types.test.js.map +1 -0
  36. package/dist/backlog/ab-reference-scanner.d.ts +10 -0
  37. package/dist/backlog/ab-reference-scanner.js +22 -0
  38. package/dist/backlog/ab-reference-scanner.js.map +1 -0
  39. package/dist/backlog/ab-reference-scanner.test.d.ts +1 -0
  40. package/dist/backlog/ab-reference-scanner.test.js +83 -0
  41. package/dist/backlog/ab-reference-scanner.test.js.map +1 -0
  42. package/dist/backlog/azure-devops-provider.d.ts +59 -0
  43. package/dist/backlog/azure-devops-provider.js +283 -0
  44. package/dist/backlog/azure-devops-provider.js.map +1 -0
  45. package/dist/backlog/backlog-provider.d.ts +13 -0
  46. package/dist/backlog/backlog-provider.js +6 -0
  47. package/dist/backlog/backlog-provider.js.map +1 -0
  48. package/dist/backlog/backlog-provider.test.d.ts +1 -0
  49. package/dist/backlog/backlog-provider.test.js +426 -0
  50. package/dist/backlog/backlog-provider.test.js.map +1 -0
  51. package/dist/backlog/clickup-provider.d.ts +55 -0
  52. package/dist/backlog/clickup-provider.js +301 -0
  53. package/dist/backlog/clickup-provider.js.map +1 -0
  54. package/dist/backlog/clickup-provider.test.d.ts +1 -0
  55. package/dist/backlog/clickup-provider.test.js +426 -0
  56. package/dist/backlog/clickup-provider.test.js.map +1 -0
  57. package/dist/backlog/clickup-reference-scanner.d.ts +10 -0
  58. package/dist/backlog/clickup-reference-scanner.js +32 -0
  59. package/dist/backlog/clickup-reference-scanner.js.map +1 -0
  60. package/dist/backlog/clickup-reference-scanner.test.d.ts +1 -0
  61. package/dist/backlog/clickup-reference-scanner.test.js +92 -0
  62. package/dist/backlog/clickup-reference-scanner.test.js.map +1 -0
  63. package/dist/backlog/code-linker.d.ts +63 -0
  64. package/dist/backlog/code-linker.js +90 -0
  65. package/dist/backlog/code-linker.js.map +1 -0
  66. package/dist/backlog/code-linker.test.d.ts +1 -0
  67. package/dist/backlog/code-linker.test.js +325 -0
  68. package/dist/backlog/code-linker.test.js.map +1 -0
  69. package/dist/backlog/index.d.ts +14 -0
  70. package/dist/backlog/index.js +8 -0
  71. package/dist/backlog/index.js.map +1 -0
  72. package/dist/backlog/jira-provider.d.ts +60 -0
  73. package/dist/backlog/jira-provider.js +272 -0
  74. package/dist/backlog/jira-provider.js.map +1 -0
  75. package/dist/backlog/jira-provider.test.d.ts +1 -0
  76. package/dist/backlog/jira-provider.test.js +449 -0
  77. package/dist/backlog/jira-provider.test.js.map +1 -0
  78. package/dist/backlog/jira-reference-scanner.d.ts +11 -0
  79. package/dist/backlog/jira-reference-scanner.js +26 -0
  80. package/dist/backlog/jira-reference-scanner.js.map +1 -0
  81. package/dist/backlog/jira-reference-scanner.test.d.ts +1 -0
  82. package/dist/backlog/jira-reference-scanner.test.js +127 -0
  83. package/dist/backlog/jira-reference-scanner.test.js.map +1 -0
  84. package/dist/backlog/types.d.ts +22 -0
  85. package/dist/backlog/types.js +1 -0
  86. package/dist/backlog/types.js.map +1 -0
  87. package/dist/chunker/ast-chunker.d.ts +45 -0
  88. package/dist/chunker/ast-chunker.js +292 -0
  89. package/dist/chunker/ast-chunker.js.map +1 -0
  90. package/dist/chunker/ast-chunker.test.d.ts +1 -0
  91. package/dist/chunker/ast-chunker.test.js +391 -0
  92. package/dist/chunker/ast-chunker.test.js.map +1 -0
  93. package/dist/chunker/chunker.d.ts +8 -0
  94. package/dist/chunker/chunker.js +1 -0
  95. package/dist/chunker/chunker.js.map +1 -0
  96. package/dist/chunker/index.d.ts +3 -0
  97. package/dist/chunker/index.js +2 -0
  98. package/dist/chunker/index.js.map +1 -0
  99. package/dist/config/config-parser.d.ts +15 -0
  100. package/dist/config/config-parser.js +283 -0
  101. package/dist/config/config-parser.js.map +1 -0
  102. package/dist/config/config-parser.test.d.ts +1 -0
  103. package/dist/config/config-parser.test.js +699 -0
  104. package/dist/config/config-parser.test.js.map +1 -0
  105. package/dist/docs/confluence-provider.d.ts +121 -0
  106. package/dist/docs/confluence-provider.js +459 -0
  107. package/dist/docs/confluence-provider.js.map +1 -0
  108. package/dist/docs/confluence-provider.test.d.ts +1 -0
  109. package/dist/docs/confluence-provider.test.js +765 -0
  110. package/dist/docs/confluence-provider.test.js.map +1 -0
  111. package/dist/docs/index.d.ts +4 -0
  112. package/dist/docs/index.js +2 -0
  113. package/dist/docs/index.js.map +1 -0
  114. package/dist/docs/sharepoint-provider.d.ts +150 -0
  115. package/dist/docs/sharepoint-provider.js +637 -0
  116. package/dist/docs/sharepoint-provider.js.map +1 -0
  117. package/dist/docs/sharepoint-provider.test.d.ts +1 -0
  118. package/dist/docs/sharepoint-provider.test.js +873 -0
  119. package/dist/docs/sharepoint-provider.test.js.map +1 -0
  120. package/dist/embedding/bm25-index.d.ts +12 -0
  121. package/dist/embedding/bm25-index.js +89 -0
  122. package/dist/embedding/bm25-index.js.map +1 -0
  123. package/dist/embedding/bm25-index.test.d.ts +1 -0
  124. package/dist/embedding/bm25-index.test.js +289 -0
  125. package/dist/embedding/bm25-index.test.js.map +1 -0
  126. package/dist/embedding/hybrid-search.d.ts +13 -0
  127. package/dist/embedding/hybrid-search.js +124 -0
  128. package/dist/embedding/hybrid-search.js.map +1 -0
  129. package/dist/embedding/hybrid-search.test.d.ts +1 -0
  130. package/dist/embedding/hybrid-search.test.js +266 -0
  131. package/dist/embedding/hybrid-search.test.js.map +1 -0
  132. package/dist/embedding/index.d.ts +11 -0
  133. package/dist/embedding/index.js +7 -0
  134. package/dist/embedding/index.js.map +1 -0
  135. package/dist/embedding/lancedb-store.d.ts +21 -0
  136. package/dist/embedding/lancedb-store.js +172 -0
  137. package/dist/embedding/lancedb-store.js.map +1 -0
  138. package/dist/embedding/lancedb-store.test.d.ts +1 -0
  139. package/dist/embedding/lancedb-store.test.js +268 -0
  140. package/dist/embedding/lancedb-store.test.js.map +1 -0
  141. package/dist/embedding/model-lifecycle-manager.d.ts +83 -0
  142. package/dist/embedding/model-lifecycle-manager.js +419 -0
  143. package/dist/embedding/model-lifecycle-manager.js.map +1 -0
  144. package/dist/embedding/model-lifecycle-manager.test.d.ts +1 -0
  145. package/dist/embedding/model-lifecycle-manager.test.js +642 -0
  146. package/dist/embedding/model-lifecycle-manager.test.js.map +1 -0
  147. package/dist/embedding/ollama-embedding-provider.d.ts +16 -0
  148. package/dist/embedding/ollama-embedding-provider.js +74 -0
  149. package/dist/embedding/ollama-embedding-provider.js.map +1 -0
  150. package/dist/embedding/ollama-embedding-provider.test.d.ts +1 -0
  151. package/dist/embedding/ollama-embedding-provider.test.js +198 -0
  152. package/dist/embedding/ollama-embedding-provider.test.js.map +1 -0
  153. package/dist/embedding/openai-compatible-embedding-provider.d.ts +19 -0
  154. package/dist/embedding/openai-compatible-embedding-provider.js +108 -0
  155. package/dist/embedding/openai-compatible-embedding-provider.js.map +1 -0
  156. package/dist/embedding/openai-compatible-embedding-provider.test.d.ts +1 -0
  157. package/dist/embedding/openai-compatible-embedding-provider.test.js +456 -0
  158. package/dist/embedding/openai-compatible-embedding-provider.test.js.map +1 -0
  159. package/dist/embedding/qdrant-store.d.ts +28 -0
  160. package/dist/embedding/qdrant-store.js +174 -0
  161. package/dist/embedding/qdrant-store.js.map +1 -0
  162. package/dist/embedding/qdrant-store.test.d.ts +1 -0
  163. package/dist/embedding/qdrant-store.test.js +359 -0
  164. package/dist/embedding/qdrant-store.test.js.map +1 -0
  165. package/dist/enrichment/index.d.ts +4 -0
  166. package/dist/enrichment/index.js +2 -0
  167. package/dist/enrichment/index.js.map +1 -0
  168. package/dist/enrichment/nl-enricher.d.ts +16 -0
  169. package/dist/enrichment/nl-enricher.js +47 -0
  170. package/dist/enrichment/nl-enricher.js.map +1 -0
  171. package/dist/enrichment/nl-enricher.test.d.ts +1 -0
  172. package/dist/enrichment/nl-enricher.test.js +154 -0
  173. package/dist/enrichment/nl-enricher.test.js.map +1 -0
  174. package/dist/enrichment/ollama-client.d.ts +18 -0
  175. package/dist/enrichment/ollama-client.js +55 -0
  176. package/dist/enrichment/ollama-client.js.map +1 -0
  177. package/dist/enrichment/ollama-client.test.d.ts +1 -0
  178. package/dist/enrichment/ollama-client.test.js +129 -0
  179. package/dist/enrichment/ollama-client.test.js.map +1 -0
  180. package/dist/git/git-client.d.ts +22 -0
  181. package/dist/git/git-client.js +6 -0
  182. package/dist/git/git-client.js.map +1 -0
  183. package/dist/git/git-client.test.d.ts +1 -0
  184. package/dist/git/git-client.test.js +200 -0
  185. package/dist/git/git-client.test.js.map +1 -0
  186. package/dist/git/ignore-filter.d.ts +2 -0
  187. package/dist/git/ignore-filter.js +31 -0
  188. package/dist/git/ignore-filter.js.map +1 -0
  189. package/dist/git/ignore-filter.test.d.ts +1 -0
  190. package/dist/git/ignore-filter.test.js +87 -0
  191. package/dist/git/ignore-filter.test.js.map +1 -0
  192. package/dist/git/index.d.ts +4 -0
  193. package/dist/git/index.js +3 -0
  194. package/dist/git/index.js.map +1 -0
  195. package/dist/git/simple-git-client.d.ts +12 -0
  196. package/dist/git/simple-git-client.js +138 -0
  197. package/dist/git/simple-git-client.js.map +1 -0
  198. package/dist/graph/cross-repo-resolver.d.ts +50 -0
  199. package/dist/graph/cross-repo-resolver.js +315 -0
  200. package/dist/graph/cross-repo-resolver.js.map +1 -0
  201. package/dist/graph/cross-repo-resolver.test.d.ts +1 -0
  202. package/dist/graph/cross-repo-resolver.test.js +548 -0
  203. package/dist/graph/cross-repo-resolver.test.js.map +1 -0
  204. package/dist/graph/dependency-graph.d.ts +44 -0
  205. package/dist/graph/dependency-graph.js +108 -0
  206. package/dist/graph/dependency-graph.js.map +1 -0
  207. package/dist/graph/dependency-graph.test.d.ts +1 -0
  208. package/dist/graph/dependency-graph.test.js +276 -0
  209. package/dist/graph/dependency-graph.test.js.map +1 -0
  210. package/dist/graph/graph-builder.d.ts +11 -0
  211. package/dist/graph/graph-builder.js +113 -0
  212. package/dist/graph/graph-builder.js.map +1 -0
  213. package/dist/graph/graph-builder.test.d.ts +1 -0
  214. package/dist/graph/graph-builder.test.js +178 -0
  215. package/dist/graph/graph-builder.test.js.map +1 -0
  216. package/dist/graph/import-resolver.d.ts +11 -0
  217. package/dist/graph/import-resolver.js +199 -0
  218. package/dist/graph/import-resolver.js.map +1 -0
  219. package/dist/graph/import-resolver.test.d.ts +1 -0
  220. package/dist/graph/import-resolver.test.js +282 -0
  221. package/dist/graph/import-resolver.test.js.map +1 -0
  222. package/dist/graph/index.d.ts +7 -0
  223. package/dist/graph/index.js +4 -0
  224. package/dist/graph/index.js.map +1 -0
  225. package/dist/index.d.ts +31 -0
  226. package/dist/index.js +15 -0
  227. package/dist/index.js.map +1 -0
  228. package/dist/indexer/file-scanner.d.ts +34 -0
  229. package/dist/indexer/file-scanner.js +69 -0
  230. package/dist/indexer/file-scanner.js.map +1 -0
  231. package/dist/indexer/file-scanner.test.d.ts +1 -0
  232. package/dist/indexer/file-scanner.test.js +110 -0
  233. package/dist/indexer/file-scanner.test.js.map +1 -0
  234. package/dist/indexer/file-watcher.d.ts +79 -0
  235. package/dist/indexer/file-watcher.js +148 -0
  236. package/dist/indexer/incremental-indexer.d.ts +67 -0
  237. package/dist/indexer/incremental-indexer.js +142 -0
  238. package/dist/indexer/incremental-indexer.js.map +1 -0
  239. package/dist/indexer/incremental-indexer.test.d.ts +1 -0
  240. package/dist/indexer/incremental-indexer.test.js +266 -0
  241. package/dist/indexer/incremental-indexer.test.js.map +1 -0
  242. package/dist/indexer/index-check.d.ts +22 -0
  243. package/dist/indexer/index-check.js +74 -0
  244. package/dist/indexer/index-check.js.map +1 -0
  245. package/dist/indexer/index-check.test.d.ts +1 -0
  246. package/dist/indexer/index-check.test.js +100 -0
  247. package/dist/indexer/index-check.test.js.map +1 -0
  248. package/dist/indexer/index-state.d.ts +61 -0
  249. package/dist/indexer/index-state.js +82 -0
  250. package/dist/indexer/index-state.js.map +1 -0
  251. package/dist/indexer/index-state.test.d.ts +1 -0
  252. package/dist/indexer/index-state.test.js +140 -0
  253. package/dist/indexer/index-state.test.js.map +1 -0
  254. package/dist/indexer/index.d.ts +12 -0
  255. package/dist/indexer/index.js +6 -0
  256. package/dist/indexer/index.js.map +1 -0
  257. package/dist/indexer/multi-repo-indexer.d.ts +63 -0
  258. package/dist/indexer/multi-repo-indexer.js +144 -0
  259. package/dist/indexer/multi-repo-indexer.js.map +1 -0
  260. package/dist/indexer/multi-repo-indexer.test.d.ts +1 -0
  261. package/dist/indexer/multi-repo-indexer.test.js +238 -0
  262. package/dist/indexer/multi-repo-indexer.test.js.map +1 -0
  263. package/dist/parser/index.d.ts +4 -0
  264. package/dist/parser/index.js +3 -0
  265. package/dist/parser/index.js.map +1 -0
  266. package/dist/parser/language-registry.d.ts +46 -0
  267. package/dist/parser/language-registry.js +219 -0
  268. package/dist/parser/language-registry.js.map +1 -0
  269. package/dist/parser/language-registry.test.d.ts +1 -0
  270. package/dist/parser/language-registry.test.js +225 -0
  271. package/dist/parser/language-registry.test.js.map +1 -0
  272. package/dist/parser/markdown-parser.d.ts +124 -0
  273. package/dist/parser/markdown-parser.js +487 -0
  274. package/dist/parser/markdown-parser.js.map +1 -0
  275. package/dist/parser/markdown-parser.test.d.ts +1 -0
  276. package/dist/parser/markdown-parser.test.js +600 -0
  277. package/dist/parser/markdown-parser.test.js.map +1 -0
  278. package/dist/parser/tree-sitter-parser.d.ts +32 -0
  279. package/dist/parser/tree-sitter-parser.js +146 -0
  280. package/dist/parser/tree-sitter-parser.js.map +1 -0
  281. package/dist/retrieval/context-expander.d.ts +51 -0
  282. package/dist/retrieval/context-expander.js +218 -0
  283. package/dist/retrieval/context-expander.js.map +1 -0
  284. package/dist/retrieval/context-expander.test.d.ts +1 -0
  285. package/dist/retrieval/context-expander.test.js +339 -0
  286. package/dist/retrieval/context-expander.test.js.map +1 -0
  287. package/dist/retrieval/cross-encoder-reranker.d.ts +16 -0
  288. package/dist/retrieval/cross-encoder-reranker.js +90 -0
  289. package/dist/retrieval/cross-encoder-reranker.js.map +1 -0
  290. package/dist/retrieval/cross-encoder-reranker.test.d.ts +1 -0
  291. package/dist/retrieval/cross-encoder-reranker.test.js +305 -0
  292. package/dist/retrieval/cross-encoder-reranker.test.js.map +1 -0
  293. package/dist/retrieval/index.d.ts +8 -0
  294. package/dist/retrieval/index.js +4 -0
  295. package/dist/retrieval/index.js.map +1 -0
  296. package/dist/retrieval/query-analyzer.d.ts +29 -0
  297. package/dist/retrieval/query-analyzer.js +238 -0
  298. package/dist/retrieval/query-analyzer.js.map +1 -0
  299. package/dist/retrieval/query-analyzer.test.d.ts +1 -0
  300. package/dist/retrieval/query-analyzer.test.js +236 -0
  301. package/dist/retrieval/query-analyzer.test.js.map +1 -0
  302. package/dist/retrieval/token-budget.d.ts +51 -0
  303. package/dist/retrieval/token-budget.js +141 -0
  304. package/dist/retrieval/token-budget.js.map +1 -0
  305. package/dist/retrieval/token-budget.test.d.ts +1 -0
  306. package/dist/retrieval/token-budget.test.js +404 -0
  307. package/dist/retrieval/token-budget.test.js.map +1 -0
  308. package/dist/storage/azure-blob-provider.d.ts +19 -0
  309. package/dist/storage/azure-blob-provider.js +199 -0
  310. package/dist/storage/azure-blob-provider.js.map +1 -0
  311. package/dist/storage/azure-blob-provider.test.d.ts +1 -0
  312. package/dist/storage/azure-blob-provider.test.js +250 -0
  313. package/dist/storage/azure-blob-provider.test.js.map +1 -0
  314. package/dist/storage/gcs-provider.d.ts +22 -0
  315. package/dist/storage/gcs-provider.js +241 -0
  316. package/dist/storage/gcs-provider.js.map +1 -0
  317. package/dist/storage/gcs-provider.test.d.ts +1 -0
  318. package/dist/storage/gcs-provider.test.js +299 -0
  319. package/dist/storage/gcs-provider.test.js.map +1 -0
  320. package/dist/storage/index.d.ts +5 -0
  321. package/dist/storage/index.js +4 -0
  322. package/dist/storage/index.js.map +1 -0
  323. package/dist/storage/s3-provider.d.ts +21 -0
  324. package/dist/storage/s3-provider.js +220 -0
  325. package/dist/storage/s3-provider.js.map +1 -0
  326. package/dist/storage/s3-provider.test.d.ts +1 -0
  327. package/dist/storage/s3-provider.test.js +329 -0
  328. package/dist/storage/s3-provider.test.js.map +1 -0
  329. package/dist/storage/types.d.ts +65 -0
  330. package/dist/storage/types.js +12 -0
  331. package/dist/storage/types.js.map +1 -0
  332. package/dist/types/chunk.d.ts +32 -0
  333. package/dist/types/chunk.js +1 -0
  334. package/dist/types/chunk.js.map +1 -0
  335. package/dist/types/config.d.ts +71 -0
  336. package/dist/types/config.js +1 -0
  337. package/dist/types/config.js.map +1 -0
  338. package/dist/types/index.d.ts +5 -0
  339. package/dist/types/index.js +1 -0
  340. package/dist/types/index.js.map +1 -0
  341. package/dist/types/provider.d.ts +54 -0
  342. package/dist/types/provider.js +36 -0
  343. package/dist/types/provider.js.map +1 -0
  344. package/dist/types/search.d.ts +27 -0
  345. package/dist/types/search.js +1 -0
  346. package/dist/types/search.js.map +1 -0
  347. package/package.json +70 -0
@@ -0,0 +1,224 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { RBACManager } from './rbac.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Helpers
5
+ // ---------------------------------------------------------------------------
6
+ function createUser(overrides) {
7
+ return {
8
+ id: 'user-1',
9
+ email: 'dev@example.com',
10
+ name: 'Test User',
11
+ roles: ['viewer'],
12
+ allowedRepos: ['repo-a', 'repo-b'],
13
+ ...overrides,
14
+ };
15
+ }
16
+ function createSearchResult(repoName) {
17
+ return {
18
+ chunkId: 'chunk-1',
19
+ content: 'function foo() {}',
20
+ nlSummary: 'A function named foo',
21
+ score: 0.95,
22
+ method: 'hybrid',
23
+ metadata: {
24
+ chunkType: 'function',
25
+ name: 'foo',
26
+ declarations: [],
27
+ imports: [],
28
+ exports: [],
29
+ repoName,
30
+ },
31
+ };
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // Tests
35
+ // ---------------------------------------------------------------------------
36
+ describe('RBACManager', () => {
37
+ const rbac = new RBACManager();
38
+ // -----------------------------------------------------------------------
39
+ // hasPermission
40
+ // -----------------------------------------------------------------------
41
+ describe('hasPermission', () => {
42
+ it('should allow viewer to search', () => {
43
+ const user = createUser({ roles: ['viewer'] });
44
+ expect(rbac.hasPermission(user, 'search')).toBe(true);
45
+ });
46
+ it('should allow viewer to access context', () => {
47
+ const user = createUser({ roles: ['viewer'] });
48
+ expect(rbac.hasPermission(user, 'context')).toBe(true);
49
+ });
50
+ it('should allow viewer to access status', () => {
51
+ const user = createUser({ roles: ['viewer'] });
52
+ expect(rbac.hasPermission(user, 'status')).toBe(true);
53
+ });
54
+ it('should deny viewer from explain', () => {
55
+ const user = createUser({ roles: ['viewer'] });
56
+ expect(rbac.hasPermission(user, 'explain')).toBe(false);
57
+ });
58
+ it('should deny viewer from docs', () => {
59
+ const user = createUser({ roles: ['viewer'] });
60
+ expect(rbac.hasPermission(user, 'docs')).toBe(false);
61
+ });
62
+ it('should deny viewer from index', () => {
63
+ const user = createUser({ roles: ['viewer'] });
64
+ expect(rbac.hasPermission(user, 'index')).toBe(false);
65
+ });
66
+ it('should deny viewer from configure', () => {
67
+ const user = createUser({ roles: ['viewer'] });
68
+ expect(rbac.hasPermission(user, 'configure')).toBe(false);
69
+ });
70
+ it('should allow developer to explain', () => {
71
+ const user = createUser({ roles: ['developer'] });
72
+ expect(rbac.hasPermission(user, 'explain')).toBe(true);
73
+ });
74
+ it('should allow developer to access docs', () => {
75
+ const user = createUser({ roles: ['developer'] });
76
+ expect(rbac.hasPermission(user, 'docs')).toBe(true);
77
+ });
78
+ it('should deny developer from index', () => {
79
+ const user = createUser({ roles: ['developer'] });
80
+ expect(rbac.hasPermission(user, 'index')).toBe(false);
81
+ });
82
+ it('should deny developer from configure', () => {
83
+ const user = createUser({ roles: ['developer'] });
84
+ expect(rbac.hasPermission(user, 'configure')).toBe(false);
85
+ });
86
+ it('should allow admin to perform all actions', () => {
87
+ const user = createUser({ roles: ['admin'] });
88
+ const allActions = [
89
+ 'search', 'context', 'status', 'explain', 'docs', 'index', 'configure',
90
+ ];
91
+ for (const action of allActions) {
92
+ expect(rbac.hasPermission(user, action)).toBe(true);
93
+ }
94
+ });
95
+ it('should allow action if any role permits it', () => {
96
+ const user = createUser({ roles: ['viewer', 'developer'] });
97
+ expect(rbac.hasPermission(user, 'explain')).toBe(true);
98
+ });
99
+ it('should deny action if no role permits it', () => {
100
+ const user = createUser({ roles: ['viewer', 'developer'] });
101
+ expect(rbac.hasPermission(user, 'index')).toBe(false);
102
+ });
103
+ });
104
+ // -----------------------------------------------------------------------
105
+ // canAccessRepo
106
+ // -----------------------------------------------------------------------
107
+ describe('canAccessRepo', () => {
108
+ it('should allow access when repo is in allowedRepos', () => {
109
+ const user = createUser({ allowedRepos: ['repo-a', 'repo-b'] });
110
+ expect(rbac.canAccessRepo(user, 'repo-a')).toBe(true);
111
+ });
112
+ it('should deny access when repo is not in allowedRepos', () => {
113
+ const user = createUser({ allowedRepos: ['repo-a'] });
114
+ expect(rbac.canAccessRepo(user, 'repo-c')).toBe(false);
115
+ });
116
+ it('should grant admin with empty allowedRepos access to all repos', () => {
117
+ const user = createUser({ roles: ['admin'], allowedRepos: [] });
118
+ expect(rbac.canAccessRepo(user, 'any-repo')).toBe(true);
119
+ });
120
+ it('should restrict non-admin with empty allowedRepos', () => {
121
+ const user = createUser({ roles: ['viewer'], allowedRepos: [] });
122
+ expect(rbac.canAccessRepo(user, 'any-repo')).toBe(false);
123
+ });
124
+ it('should restrict admin with explicit allowedRepos to those repos', () => {
125
+ const user = createUser({
126
+ roles: ['admin'],
127
+ allowedRepos: ['repo-x'],
128
+ });
129
+ expect(rbac.canAccessRepo(user, 'repo-x')).toBe(true);
130
+ expect(rbac.canAccessRepo(user, 'repo-y')).toBe(false);
131
+ });
132
+ });
133
+ // -----------------------------------------------------------------------
134
+ // filterResultsByAccess
135
+ // -----------------------------------------------------------------------
136
+ describe('filterResultsByAccess', () => {
137
+ it('should keep results from allowed repos', () => {
138
+ const user = createUser({ allowedRepos: ['repo-a'] });
139
+ const results = [
140
+ createSearchResult('repo-a'),
141
+ createSearchResult('repo-b'),
142
+ ];
143
+ const filtered = rbac.filterResultsByAccess(user, results);
144
+ expect(filtered).toHaveLength(1);
145
+ expect(filtered[0].metadata.repoName).toBe('repo-a');
146
+ });
147
+ it('should keep results without repoName (single-repo mode)', () => {
148
+ const user = createUser({ allowedRepos: ['repo-a'] });
149
+ const results = [
150
+ createSearchResult(undefined),
151
+ createSearchResult('repo-b'),
152
+ ];
153
+ const filtered = rbac.filterResultsByAccess(user, results);
154
+ expect(filtered).toHaveLength(1);
155
+ expect(filtered[0].metadata.repoName).toBeUndefined();
156
+ });
157
+ it('should return all results for admin with empty allowedRepos', () => {
158
+ const user = createUser({ roles: ['admin'], allowedRepos: [] });
159
+ const results = [
160
+ createSearchResult('repo-a'),
161
+ createSearchResult('repo-b'),
162
+ createSearchResult('repo-c'),
163
+ ];
164
+ const filtered = rbac.filterResultsByAccess(user, results);
165
+ expect(filtered).toHaveLength(3);
166
+ });
167
+ it('should return empty array when no results match', () => {
168
+ const user = createUser({ allowedRepos: ['repo-x'] });
169
+ const results = [
170
+ createSearchResult('repo-a'),
171
+ createSearchResult('repo-b'),
172
+ ];
173
+ const filtered = rbac.filterResultsByAccess(user, results);
174
+ expect(filtered).toHaveLength(0);
175
+ });
176
+ it('should not mutate the original results array', () => {
177
+ const user = createUser({ allowedRepos: ['repo-a'] });
178
+ const results = [
179
+ createSearchResult('repo-a'),
180
+ createSearchResult('repo-b'),
181
+ ];
182
+ const originalLength = results.length;
183
+ rbac.filterResultsByAccess(user, results);
184
+ expect(results).toHaveLength(originalLength);
185
+ });
186
+ });
187
+ // -----------------------------------------------------------------------
188
+ // getRoleLevel & getHighestRole
189
+ // -----------------------------------------------------------------------
190
+ describe('getRoleLevel', () => {
191
+ it('should return 0 for viewer', () => {
192
+ expect(rbac.getRoleLevel('viewer')).toBe(0);
193
+ });
194
+ it('should return 1 for developer', () => {
195
+ expect(rbac.getRoleLevel('developer')).toBe(1);
196
+ });
197
+ it('should return 2 for admin', () => {
198
+ expect(rbac.getRoleLevel('admin')).toBe(2);
199
+ });
200
+ it('should maintain hierarchy ordering', () => {
201
+ expect(rbac.getRoleLevel('admin')).toBeGreaterThan(rbac.getRoleLevel('developer'));
202
+ expect(rbac.getRoleLevel('developer')).toBeGreaterThan(rbac.getRoleLevel('viewer'));
203
+ });
204
+ });
205
+ describe('getHighestRole', () => {
206
+ it('should return admin when user has admin role', () => {
207
+ const user = createUser({ roles: ['viewer', 'admin'] });
208
+ expect(rbac.getHighestRole(user)).toBe('admin');
209
+ });
210
+ it('should return developer when user has developer but not admin', () => {
211
+ const user = createUser({ roles: ['viewer', 'developer'] });
212
+ expect(rbac.getHighestRole(user)).toBe('developer');
213
+ });
214
+ it('should return viewer when that is the only role', () => {
215
+ const user = createUser({ roles: ['viewer'] });
216
+ expect(rbac.getHighestRole(user)).toBe('viewer');
217
+ });
218
+ it('should return admin when all roles are present', () => {
219
+ const user = createUser({ roles: ['viewer', 'developer', 'admin'] });
220
+ expect(rbac.getHighestRole(user)).toBe('admin');
221
+ });
222
+ });
223
+ });
224
+ //# sourceMappingURL=rbac.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rbac.test.js","sourceRoot":"","sources":["../../src/auth/rbac.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAIxC,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,UAAU,CAAC,SAAyB;IAC3C,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,iBAAiB;QACxB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,CAAC,QAAQ,CAAC;QACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAiB;IAC3C,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE,mBAAmB;QAC5B,SAAS,EAAE,sBAAsB;QACjC,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE;YACR,SAAS,EAAE,UAAU;YACrB,IAAI,EAAE,KAAK;YACX,YAAY,EAAE,EAAE;YAChB,OAAO,EAAE,EAAE;YACX,OAAO,EAAE,EAAE;YACX,QAAQ;SACT;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;IAE/B,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAE1E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAa;gBAC3B,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;aACvE,CAAC;YACF,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,gBAAgB;IAChB,0EAA0E;IAE1E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;YAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;YAC7D,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;YACxE,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YACjE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YACzE,MAAM,IAAI,GAAG,UAAU,CAAC;gBACtB,KAAK,EAAE,CAAC,OAAO,CAAC;gBAChB,YAAY,EAAE,CAAC,QAAQ,CAAC;aACzB,CAAC,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,wBAAwB;IACxB,0EAA0E;IAE1E,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG;gBACd,kBAAkB,CAAC,QAAQ,CAAC;gBAC5B,kBAAkB,CAAC,QAAQ,CAAC;aAC7B,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3D,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;YACjE,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG;gBACd,kBAAkB,CAAC,SAAS,CAAC;gBAC7B,kBAAkB,CAAC,QAAQ,CAAC;aAC7B,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3D,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;YACrE,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YAChE,MAAM,OAAO,GAAG;gBACd,kBAAkB,CAAC,QAAQ,CAAC;gBAC5B,kBAAkB,CAAC,QAAQ,CAAC;gBAC5B,kBAAkB,CAAC,QAAQ,CAAC;aAC7B,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3D,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG;gBACd,kBAAkB,CAAC,QAAQ,CAAC;gBAC5B,kBAAkB,CAAC,QAAQ,CAAC;aAC7B,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3D,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG;gBACd,kBAAkB,CAAC,QAAQ,CAAC;gBAC5B,kBAAkB,CAAC,QAAQ,CAAC;aAC7B,CAAC;YACF,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;YACtC,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,0EAA0E;IAC1E,gCAAgC;IAChC,0EAA0E;IAE1E,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;YACnF,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;YACxD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { AuthProvider, AuthToken, Role, SAMLConfig, User } from './types.js';
3
+ import { AuthError } from './types.js';
4
+ /**
5
+ * SAML 2.0 `AuthProvider` implementation.
6
+ *
7
+ * Handles AuthnRequest generation and SAML Response validation for
8
+ * enterprise SSO integration.
9
+ */
10
+ export declare class SAMLProvider implements AuthProvider {
11
+ readonly name = "saml";
12
+ private readonly config;
13
+ private idpMetadata;
14
+ /** Users whose info has been resolved (in-memory cache). */
15
+ private readonly userCache;
16
+ /** Counter for unique AuthnRequest IDs. */
17
+ private requestCounter;
18
+ /**
19
+ * Pluggable `fetch` function — defaults to the global `fetch`.
20
+ */
21
+ private readonly fetchFn;
22
+ constructor(config: SAMLConfig, fetchFn?: typeof fetch);
23
+ /**
24
+ * Fetches and parses IdP metadata XML to discover SSO URL, certificate,
25
+ * and NameID format.
26
+ */
27
+ initialize(): Promise<Result<void, AuthError>>;
28
+ /**
29
+ * Creates a SAML AuthnRequest and returns the IdP redirect URL together
30
+ * with the request ID (for later response correlation).
31
+ */
32
+ generateAuthRequest(): Result<{
33
+ url: string;
34
+ id: string;
35
+ }, AuthError>;
36
+ authenticate(token: string): Promise<Result<AuthToken, AuthError>>;
37
+ getUserRoles(userId: string): Promise<Result<readonly Role[], AuthError>>;
38
+ getUserRepos(userId: string): Promise<Result<readonly string[], AuthError>>;
39
+ /**
40
+ * Validates a Base64-encoded SAML Response: checks XML signature,
41
+ * conditions (audience, timestamps), and extracts the user.
42
+ */
43
+ validateResponse(samlResponseB64: string): Promise<Result<User, AuthError>>;
44
+ /**
45
+ * Maps SAML attributes to a CodeRAG `User`.
46
+ */
47
+ mapAttributes(attributes: Readonly<Record<string, string>>, xml?: string): User;
48
+ private mapRoleValues;
49
+ private verifyXmlSignature;
50
+ private checkConditions;
51
+ }
@@ -0,0 +1,355 @@
1
+ import { ok, err } from 'neverthrow';
2
+ import { createVerify } from 'node:crypto';
3
+ import { AuthError } from './types.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Extracts the text content of the first occurrence of `tagName` from XML.
9
+ * This is a minimal, dependency-free XML "parser" — it does **not** handle
10
+ * namespaces, CDATA, or nested elements with the same local name. Sufficient
11
+ * for SAML metadata / response parsing.
12
+ */
13
+ function xmlGetText(xml, tagName) {
14
+ // Match both with and without namespace prefix
15
+ const localName = tagName.includes(':') ? tagName.split(':').pop() : tagName;
16
+ // Try with namespace prefix first, then without
17
+ for (const name of [tagName, localName]) {
18
+ const esc = escapeRegex(name);
19
+ const patterns = [
20
+ // <ns:Tag ...>content</ns:Tag> — requires tag name to end at > or whitespace
21
+ new RegExp(`<(?:[a-zA-Z0-9_]+:)?${esc}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:[a-zA-Z0-9_]+:)?${esc}>`, 'i'),
22
+ // <Tag ...>content</Tag>
23
+ new RegExp(`<${esc}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${esc}>`, 'i'),
24
+ ];
25
+ for (const re of patterns) {
26
+ const match = re.exec(xml);
27
+ if (match?.[1] !== undefined) {
28
+ return match[1].trim();
29
+ }
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Extracts an attribute value from the first element matching `tagName`.
36
+ */
37
+ function xmlGetAttr(xml, tagName, attrName) {
38
+ const localName = tagName.includes(':') ? tagName.split(':').pop() : tagName;
39
+ for (const name of [tagName, localName]) {
40
+ const esc = escapeRegex(name);
41
+ // Match <ns:Tag ...> or <Tag ...> — tag name must be followed by whitespace, /, or >
42
+ const tagRe = new RegExp(`<(?:[a-zA-Z0-9_]+:)?${esc}(?:\\s[^>]*|\\/)?>`, 'i');
43
+ const tagMatch = tagRe.exec(xml);
44
+ if (tagMatch) {
45
+ const attrRe = new RegExp(`${escapeRegex(attrName)}\\s*=\\s*"([^"]*)"`, 'i');
46
+ const attrMatch = attrRe.exec(tagMatch[0]);
47
+ if (attrMatch?.[1] !== undefined) {
48
+ return attrMatch[1];
49
+ }
50
+ }
51
+ }
52
+ return undefined;
53
+ }
54
+ /**
55
+ * Extracts all SAML attribute values from a SAML Response.
56
+ * Returns a map of attribute name -> value.
57
+ */
58
+ function extractSamlAttributes(xml) {
59
+ const attrs = {};
60
+ // Match <saml:Attribute Name="..."><saml:AttributeValue>...</saml:AttributeValue></saml:Attribute>
61
+ const attrRe = /<[^>]*?Attribute\s[^>]*?Name\s*=\s*"([^"]*)"[^>]*?>([\s\S]*?)<\/[^>]*?Attribute>/gi;
62
+ let attrMatch = attrRe.exec(xml);
63
+ while (attrMatch) {
64
+ const name = attrMatch[1];
65
+ const body = attrMatch[2];
66
+ if (name && body) {
67
+ // Get the first AttributeValue
68
+ const valueRe = /<[^>]*?AttributeValue[^>]*?>([\s\S]*?)<\/[^>]*?AttributeValue>/i;
69
+ const valueMatch = valueRe.exec(body);
70
+ if (valueMatch?.[1] !== undefined) {
71
+ attrs[name] = valueMatch[1].trim();
72
+ }
73
+ }
74
+ attrMatch = attrRe.exec(xml);
75
+ }
76
+ return attrs;
77
+ }
78
+ function escapeRegex(str) {
79
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // SAMLProvider
83
+ // ---------------------------------------------------------------------------
84
+ /**
85
+ * SAML 2.0 `AuthProvider` implementation.
86
+ *
87
+ * Handles AuthnRequest generation and SAML Response validation for
88
+ * enterprise SSO integration.
89
+ */
90
+ export class SAMLProvider {
91
+ name = 'saml';
92
+ config;
93
+ idpMetadata;
94
+ /** Users whose info has been resolved (in-memory cache). */
95
+ userCache = new Map();
96
+ /** Counter for unique AuthnRequest IDs. */
97
+ requestCounter = 0;
98
+ /**
99
+ * Pluggable `fetch` function — defaults to the global `fetch`.
100
+ */
101
+ fetchFn;
102
+ constructor(config, fetchFn) {
103
+ this.config = config;
104
+ this.fetchFn = fetchFn ?? globalThis.fetch;
105
+ }
106
+ // -----------------------------------------------------------------------
107
+ // Initialization
108
+ // -----------------------------------------------------------------------
109
+ /**
110
+ * Fetches and parses IdP metadata XML to discover SSO URL, certificate,
111
+ * and NameID format.
112
+ */
113
+ async initialize() {
114
+ try {
115
+ const response = await this.fetchFn(this.config.idpMetadataUrl);
116
+ if (!response.ok) {
117
+ return err(new AuthError(`IdP metadata fetch failed: HTTP ${String(response.status)}`));
118
+ }
119
+ const xml = await response.text();
120
+ const entityId = xmlGetAttr(xml, 'EntityDescriptor', 'entityID') ?? '';
121
+ const ssoUrl = xmlGetAttr(xml, 'SingleSignOnService', 'Location') ?? '';
122
+ const certificate = xmlGetText(xml, 'X509Certificate') ?? '';
123
+ const nameIdFormat = xmlGetText(xml, 'NameIDFormat') ??
124
+ 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
125
+ if (!entityId || !ssoUrl || !certificate) {
126
+ return err(new AuthError('IdP metadata missing required fields (entityID, SSO URL, or certificate)'));
127
+ }
128
+ this.idpMetadata = { entityId, ssoUrl, certificate, nameIdFormat };
129
+ return ok(undefined);
130
+ }
131
+ catch (cause) {
132
+ const message = cause instanceof Error ? cause.message : 'Unknown error';
133
+ return err(new AuthError(`SAML initialization error: ${message}`));
134
+ }
135
+ }
136
+ // -----------------------------------------------------------------------
137
+ // AuthnRequest generation
138
+ // -----------------------------------------------------------------------
139
+ /**
140
+ * Creates a SAML AuthnRequest and returns the IdP redirect URL together
141
+ * with the request ID (for later response correlation).
142
+ */
143
+ generateAuthRequest() {
144
+ if (!this.idpMetadata) {
145
+ return err(new AuthError('SAML not initialized — call initialize() first'));
146
+ }
147
+ this.requestCounter += 1;
148
+ const id = `_coderag_${Date.now()}_${String(this.requestCounter)}`;
149
+ const issueInstant = new Date().toISOString();
150
+ const authnRequest = [
151
+ '<samlp:AuthnRequest',
152
+ ' xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"',
153
+ ' xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"',
154
+ ` ID="${id}"`,
155
+ ' Version="2.0"',
156
+ ` IssueInstant="${issueInstant}"`,
157
+ ` Destination="${this.idpMetadata.ssoUrl}"`,
158
+ ` AssertionConsumerServiceURL="${this.config.spAcsUrl}"`,
159
+ ` ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">`,
160
+ ` <saml:Issuer>${this.config.spEntityId}</saml:Issuer>`,
161
+ ' <samlp:NameIDPolicy',
162
+ ` Format="${this.idpMetadata.nameIdFormat}"`,
163
+ ' AllowCreate="true" />',
164
+ '</samlp:AuthnRequest>',
165
+ ].join('\n');
166
+ const encoded = Buffer.from(authnRequest).toString('base64');
167
+ const separator = this.idpMetadata.ssoUrl.includes('?') ? '&' : '?';
168
+ const url = `${this.idpMetadata.ssoUrl}${separator}SAMLRequest=${encodeURIComponent(encoded)}`;
169
+ return ok({ url, id });
170
+ }
171
+ // -----------------------------------------------------------------------
172
+ // AuthProvider implementation
173
+ // -----------------------------------------------------------------------
174
+ async authenticate(token) {
175
+ const userResult = await this.validateResponse(token);
176
+ if (userResult.isErr()) {
177
+ return err(userResult.error);
178
+ }
179
+ const user = userResult.value;
180
+ const now = Math.floor(Date.now() / 1000);
181
+ const authToken = {
182
+ userId: user.id,
183
+ email: user.email,
184
+ roles: user.roles,
185
+ exp: now + 3600, // 1 hour default
186
+ iat: now,
187
+ };
188
+ return ok(authToken);
189
+ }
190
+ async getUserRoles(userId) {
191
+ const user = this.userCache.get(userId);
192
+ if (user) {
193
+ return ok(user.roles);
194
+ }
195
+ return ok(['viewer']);
196
+ }
197
+ async getUserRepos(userId) {
198
+ const user = this.userCache.get(userId);
199
+ if (user) {
200
+ return ok(user.allowedRepos);
201
+ }
202
+ return ok([]);
203
+ }
204
+ // -----------------------------------------------------------------------
205
+ // SAML Response validation
206
+ // -----------------------------------------------------------------------
207
+ /**
208
+ * Validates a Base64-encoded SAML Response: checks XML signature,
209
+ * conditions (audience, timestamps), and extracts the user.
210
+ */
211
+ async validateResponse(samlResponseB64) {
212
+ let xml;
213
+ try {
214
+ xml = Buffer.from(samlResponseB64, 'base64').toString('utf-8');
215
+ }
216
+ catch {
217
+ return err(new AuthError('Invalid Base64 SAML response'));
218
+ }
219
+ // Verify signature
220
+ const sigResult = this.verifyXmlSignature(xml);
221
+ if (sigResult.isErr()) {
222
+ return err(sigResult.error);
223
+ }
224
+ // Check conditions
225
+ const condResult = this.checkConditions(xml);
226
+ if (condResult.isErr()) {
227
+ return err(condResult.error);
228
+ }
229
+ // Extract user
230
+ const user = this.mapAttributes(extractSamlAttributes(xml), xml);
231
+ this.userCache.set(user.id, user);
232
+ return ok(user);
233
+ }
234
+ // -----------------------------------------------------------------------
235
+ // Attribute mapping
236
+ // -----------------------------------------------------------------------
237
+ /**
238
+ * Maps SAML attributes to a CodeRAG `User`.
239
+ */
240
+ mapAttributes(attributes, xml) {
241
+ const email = attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] ??
242
+ attributes['email'] ??
243
+ attributes['Email'] ??
244
+ '';
245
+ const name = attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'] ??
246
+ attributes['displayName'] ??
247
+ attributes['name'] ??
248
+ email;
249
+ // Extract NameID as user ID
250
+ let nameId = '';
251
+ if (xml) {
252
+ nameId = xmlGetText(xml, 'NameID') ?? '';
253
+ }
254
+ const id = nameId || email;
255
+ // Map roles
256
+ const roleAttr = attributes['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] ??
257
+ attributes['role'] ??
258
+ attributes['Role'] ??
259
+ '';
260
+ const roles = this.mapRoleValues(roleAttr);
261
+ return { id, email, name, roles, allowedRepos: [] };
262
+ }
263
+ // -----------------------------------------------------------------------
264
+ // Private helpers
265
+ // -----------------------------------------------------------------------
266
+ mapRoleValues(roleValue) {
267
+ const mapping = this.config.roleMapping ?? {};
268
+ const values = roleValue
269
+ .split(',')
270
+ .map((v) => v.trim())
271
+ .filter(Boolean);
272
+ const roles = new Set();
273
+ for (const value of values) {
274
+ const mapped = mapping[value];
275
+ if (mapped) {
276
+ roles.add(mapped);
277
+ }
278
+ if (value === 'admin' || value === 'developer' || value === 'viewer') {
279
+ roles.add(value);
280
+ }
281
+ }
282
+ if (roles.size === 0) {
283
+ return ['viewer'];
284
+ }
285
+ return [...roles];
286
+ }
287
+ verifyXmlSignature(xml) {
288
+ if (!this.idpMetadata) {
289
+ return err(new AuthError('SAML not initialized — call initialize() first'));
290
+ }
291
+ // Extract SignatureValue and signed content
292
+ const signatureValue = xmlGetText(xml, 'SignatureValue');
293
+ if (!signatureValue) {
294
+ return err(new AuthError('No SignatureValue found in SAML response'));
295
+ }
296
+ // Extract the signed content (the Assertion element)
297
+ const digestValue = xmlGetText(xml, 'DigestValue');
298
+ if (!digestValue) {
299
+ return err(new AuthError('No DigestValue found in SAML response'));
300
+ }
301
+ // Verify using the IdP certificate or public key
302
+ const signedInfo = xmlGetText(xml, 'SignedInfo');
303
+ if (!signedInfo) {
304
+ return err(new AuthError('No SignedInfo found in SAML response'));
305
+ }
306
+ // Reconstruct SignedInfo as canonical XML for verification
307
+ const signedInfoXml = `<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">${signedInfo}</SignedInfo>`;
308
+ const signature = Buffer.from(signatureValue.replace(/\s/g, ''), 'base64');
309
+ // Try certificate format first, then public key format
310
+ const keyFormats = [
311
+ `-----BEGIN CERTIFICATE-----\n${this.idpMetadata.certificate}\n-----END CERTIFICATE-----`,
312
+ `-----BEGIN PUBLIC KEY-----\n${this.idpMetadata.certificate}\n-----END PUBLIC KEY-----`,
313
+ ];
314
+ for (const keyPem of keyFormats) {
315
+ try {
316
+ const verifier = createVerify('RSA-SHA256');
317
+ verifier.update(signedInfoXml);
318
+ const valid = verifier.verify(keyPem, signature);
319
+ if (valid) {
320
+ return ok(undefined);
321
+ }
322
+ return err(new AuthError('Invalid SAML response signature'));
323
+ }
324
+ catch {
325
+ // Try next format
326
+ continue;
327
+ }
328
+ }
329
+ return err(new AuthError('Signature verification failed: unsupported key format'));
330
+ }
331
+ checkConditions(xml) {
332
+ // Check NotBefore / NotOnOrAfter
333
+ const notBeforeStr = xmlGetAttr(xml, 'Conditions', 'NotBefore');
334
+ const notOnOrAfterStr = xmlGetAttr(xml, 'Conditions', 'NotOnOrAfter');
335
+ const now = new Date();
336
+ if (notBeforeStr) {
337
+ const notBefore = new Date(notBeforeStr);
338
+ if (now < notBefore) {
339
+ return err(new AuthError('SAML assertion not yet valid'));
340
+ }
341
+ }
342
+ if (notOnOrAfterStr) {
343
+ const notOnOrAfter = new Date(notOnOrAfterStr);
344
+ if (now >= notOnOrAfter) {
345
+ return err(new AuthError('SAML assertion expired'));
346
+ }
347
+ }
348
+ // Check audience restriction
349
+ const audience = xmlGetText(xml, 'Audience');
350
+ if (audience && audience !== this.config.spEntityId) {
351
+ return err(new AuthError(`SAML audience mismatch: expected ${this.config.spEntityId}, got ${audience}`));
352
+ }
353
+ return ok(undefined);
354
+ }
355
+ }