@softtechai/quickmcp 1.0.14 → 1.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 (417) hide show
  1. package/dist/auth/auth-utils.d.ts +130 -0
  2. package/dist/auth/auth-utils.d.ts.map +1 -0
  3. package/dist/auth/auth-utils.js +600 -0
  4. package/dist/auth/auth-utils.js.map +1 -0
  5. package/dist/auth/jwks-provider.d.ts +9 -0
  6. package/dist/auth/jwks-provider.d.ts.map +1 -0
  7. package/dist/auth/jwks-provider.js +56 -0
  8. package/dist/auth/jwks-provider.js.map +1 -0
  9. package/dist/auth/token-utils.d.ts +40 -0
  10. package/dist/auth/token-utils.d.ts.map +1 -0
  11. package/dist/auth/token-utils.js +162 -0
  12. package/dist/auth/token-utils.js.map +1 -0
  13. package/dist/client/MCPClient.js +5 -4
  14. package/dist/client/MCPClient.js.map +1 -1
  15. package/dist/config/auth-config.d.ts +16 -0
  16. package/dist/config/auth-config.d.ts.map +1 -0
  17. package/dist/config/auth-config.js +107 -0
  18. package/dist/config/auth-config.js.map +1 -0
  19. package/dist/constant/constant.d.ts +20 -0
  20. package/dist/constant/constant.d.ts.map +1 -0
  21. package/dist/constant/constant.js +24 -0
  22. package/dist/constant/constant.js.map +1 -0
  23. package/dist/database/async-datastore.d.ts +6 -0
  24. package/dist/database/async-datastore.d.ts.map +1 -0
  25. package/dist/database/async-datastore.js +15 -0
  26. package/dist/database/async-datastore.js.map +1 -0
  27. package/dist/database/database-utils.d.ts +6 -0
  28. package/dist/database/database-utils.d.ts.map +1 -0
  29. package/dist/database/database-utils.js +29 -0
  30. package/dist/database/database-utils.js.map +1 -0
  31. package/dist/database/datastore.d.ts +164 -0
  32. package/dist/database/datastore.d.ts.map +1 -0
  33. package/dist/{parsers/types/index.js → database/datastore.js} +1 -0
  34. package/dist/database/datastore.js.map +1 -0
  35. package/dist/database/factory.d.ts +4 -0
  36. package/dist/database/factory.d.ts.map +1 -0
  37. package/dist/database/factory.js +32 -0
  38. package/dist/database/factory.js.map +1 -0
  39. package/dist/database/jdbc-manager.d.ts +49 -0
  40. package/dist/database/jdbc-manager.d.ts.map +1 -0
  41. package/dist/database/jdbc-manager.js +50 -0
  42. package/dist/database/jdbc-manager.js.map +1 -0
  43. package/dist/database/sqlite-manager.d.ts +46 -44
  44. package/dist/database/sqlite-manager.d.ts.map +1 -1
  45. package/dist/database/sqlite-manager.js +492 -42
  46. package/dist/database/sqlite-manager.js.map +1 -1
  47. package/dist/database/supabase-manager.d.ts +58 -0
  48. package/dist/database/supabase-manager.d.ts.map +1 -0
  49. package/dist/database/supabase-manager.js +432 -0
  50. package/dist/database/supabase-manager.js.map +1 -0
  51. package/dist/generators/MCPServerGenerator.d.ts +103 -20
  52. package/dist/generators/MCPServerGenerator.d.ts.map +1 -1
  53. package/dist/generators/MCPServerGenerator.js +6930 -128
  54. package/dist/generators/MCPServerGenerator.js.map +1 -1
  55. package/dist/index.d.ts +0 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +8 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/integrated-mcp-server-new.d.ts +14 -2
  60. package/dist/integrated-mcp-server-new.d.ts.map +1 -1
  61. package/dist/integrated-mcp-server-new.js +270 -180
  62. package/dist/integrated-mcp-server-new.js.map +1 -1
  63. package/dist/mcp-core/McpCoreService.d.ts +63 -0
  64. package/dist/mcp-core/McpCoreService.d.ts.map +1 -0
  65. package/dist/mcp-core/McpCoreService.js +492 -0
  66. package/dist/mcp-core/McpCoreService.js.map +1 -0
  67. package/dist/parsers/CsvParser.d.ts +1 -1
  68. package/dist/parsers/CsvParser.d.ts.map +1 -1
  69. package/dist/parsers/CsvParser.js +3 -2
  70. package/dist/parsers/CsvParser.js.map +1 -1
  71. package/dist/parsers/DatabaseParser.d.ts.map +1 -1
  72. package/dist/parsers/DatabaseParser.js +9 -8
  73. package/dist/parsers/DatabaseParser.js.map +1 -1
  74. package/dist/parsers/ExcelParser.d.ts +15 -0
  75. package/dist/parsers/ExcelParser.d.ts.map +1 -1
  76. package/dist/parsers/ExcelParser.js +287 -21
  77. package/dist/parsers/ExcelParser.js.map +1 -1
  78. package/dist/parsers/WebPageParser.d.ts +5 -0
  79. package/dist/parsers/WebPageParser.d.ts.map +1 -0
  80. package/dist/parsers/WebPageParser.js +35 -0
  81. package/dist/parsers/WebPageParser.js.map +1 -0
  82. package/dist/parsers/index.d.ts +3 -2
  83. package/dist/parsers/index.d.ts.map +1 -1
  84. package/dist/parsers/index.js +19 -16
  85. package/dist/parsers/index.js.map +1 -1
  86. package/dist/server/api/askApi.d.ts +41 -0
  87. package/dist/server/api/askApi.d.ts.map +1 -0
  88. package/dist/server/api/askApi.js +479 -0
  89. package/dist/server/api/askApi.js.map +1 -0
  90. package/dist/server/api/authApi.d.ts +101 -0
  91. package/dist/server/api/authApi.d.ts.map +1 -0
  92. package/dist/server/api/authApi.js +1472 -0
  93. package/dist/server/api/authApi.js.map +1 -0
  94. package/dist/server/api/authProperty.d.ts +18 -0
  95. package/dist/server/api/authProperty.d.ts.map +1 -0
  96. package/dist/server/api/authProperty.js +41 -0
  97. package/dist/server/api/authProperty.js.map +1 -0
  98. package/dist/server/api/configApi.d.ts +15 -0
  99. package/dist/server/api/configApi.d.ts.map +1 -0
  100. package/dist/server/api/configApi.js +42 -0
  101. package/dist/server/api/configApi.js.map +1 -0
  102. package/dist/server/api/databaseApi.d.ts +14 -0
  103. package/dist/server/api/databaseApi.d.ts.map +1 -0
  104. package/dist/server/api/databaseApi.js +111 -0
  105. package/dist/server/api/databaseApi.js.map +1 -0
  106. package/dist/server/api/directoryApi.d.ts +9 -0
  107. package/dist/server/api/directoryApi.d.ts.map +1 -0
  108. package/dist/server/api/directoryApi.js +103 -0
  109. package/dist/server/api/directoryApi.js.map +1 -0
  110. package/dist/server/api/generateApi.d.ts +24 -0
  111. package/dist/server/api/generateApi.d.ts.map +1 -0
  112. package/dist/server/api/generateApi.js +457 -0
  113. package/dist/server/api/generateApi.js.map +1 -0
  114. package/dist/server/api/healthApi.d.ts +9 -0
  115. package/dist/server/api/healthApi.d.ts.map +1 -0
  116. package/dist/server/api/healthApi.js +15 -0
  117. package/dist/server/api/healthApi.js.map +1 -0
  118. package/dist/server/api/indexApi.d.ts +21 -0
  119. package/dist/server/api/indexApi.d.ts.map +1 -0
  120. package/dist/server/api/indexApi.js +61 -0
  121. package/dist/server/api/indexApi.js.map +1 -0
  122. package/dist/server/api/logsApi.d.ts +12 -0
  123. package/dist/server/api/logsApi.d.ts.map +1 -0
  124. package/dist/server/api/logsApi.js +37 -0
  125. package/dist/server/api/logsApi.js.map +1 -0
  126. package/dist/server/api/mcpApi.d.ts +20 -0
  127. package/dist/server/api/mcpApi.d.ts.map +1 -0
  128. package/dist/server/api/mcpApi.js +120 -0
  129. package/dist/server/api/mcpApi.js.map +1 -0
  130. package/dist/server/api/nameApi.d.ts +21 -0
  131. package/dist/server/api/nameApi.d.ts.map +1 -0
  132. package/dist/server/api/nameApi.js +42 -0
  133. package/dist/server/api/nameApi.js.map +1 -0
  134. package/dist/server/api/parseApi.d.ts +9 -0
  135. package/dist/server/api/parseApi.d.ts.map +1 -0
  136. package/dist/server/api/parseApi.js +3245 -0
  137. package/dist/server/api/parseApi.js.map +1 -0
  138. package/dist/server/api/serverApi.d.ts +44 -0
  139. package/dist/server/api/serverApi.d.ts.map +1 -0
  140. package/dist/server/api/serverApi.js +417 -0
  141. package/dist/server/api/serverApi.js.map +1 -0
  142. package/dist/{dynamic-mcp-executor.d.ts → server/dynamic-mcp-executor.d.ts} +4 -5
  143. package/dist/server/dynamic-mcp-executor.d.ts.map +1 -0
  144. package/dist/server/dynamic-mcp-executor.js +62 -0
  145. package/dist/server/dynamic-mcp-executor.js.map +1 -0
  146. package/dist/server/port-utils.d.ts +14 -0
  147. package/dist/server/port-utils.d.ts.map +1 -0
  148. package/dist/server/port-utils.js +31 -0
  149. package/dist/server/port-utils.js.map +1 -0
  150. package/dist/server/server-utils.d.ts +13 -0
  151. package/dist/server/server-utils.d.ts.map +1 -0
  152. package/dist/server/server-utils.js +72 -0
  153. package/dist/server/server-utils.js.map +1 -0
  154. package/dist/{web → server}/server.d.ts +1 -0
  155. package/dist/server/server.d.ts.map +1 -0
  156. package/dist/server/server.js +535 -0
  157. package/dist/server/server.js.map +1 -0
  158. package/dist/server/tool-executer.d.ts +101 -0
  159. package/dist/server/tool-executer.d.ts.map +1 -0
  160. package/dist/server/tool-executer.js +6198 -0
  161. package/dist/server/tool-executer.js.map +1 -0
  162. package/dist/types/index.d.ts +1197 -4
  163. package/dist/types/index.d.ts.map +1 -1
  164. package/dist/types/index.js +1028 -0
  165. package/dist/types/index.js.map +1 -1
  166. package/dist/upload/upload-utils.d.ts +4 -0
  167. package/dist/upload/upload-utils.d.ts.map +1 -0
  168. package/dist/upload/upload-utils.js +29 -0
  169. package/dist/upload/upload-utils.js.map +1 -0
  170. package/dist/utils/deployment-util.d.ts +14 -0
  171. package/dist/utils/deployment-util.d.ts.map +1 -0
  172. package/dist/utils/deployment-util.js +46 -0
  173. package/dist/utils/deployment-util.js.map +1 -0
  174. package/dist/utils/logger.d.ts +15 -0
  175. package/dist/utils/logger.d.ts.map +1 -0
  176. package/dist/utils/logger.js +56 -0
  177. package/dist/utils/logger.js.map +1 -0
  178. package/package.json +18 -6
  179. package/quickmcp-direct-stdio.js +245 -163
  180. package/src/web/public/app.js +15370 -1471
  181. package/src/web/public/authorization.html +868 -0
  182. package/src/web/public/database-tables.html +283 -547
  183. package/src/web/public/how-to-use.html +446 -462
  184. package/src/web/public/how-to-use.js +4 -4
  185. package/src/web/public/images/app/activepieces.png +0 -0
  186. package/src/web/public/images/app/airtable.png +0 -0
  187. package/src/web/public/images/app/androidstudio.png +0 -0
  188. package/src/web/public/images/app/antigravity.png +0 -0
  189. package/src/web/public/images/app/applenotes.png +0 -0
  190. package/src/web/public/images/app/applereminders.png +0 -0
  191. package/src/web/public/images/app/asana.png +0 -0
  192. package/src/web/public/images/app/azureai.png +0 -0
  193. package/src/web/public/images/app/bash.png +0 -0
  194. package/src/web/public/images/app/bearnotes.png +0 -0
  195. package/src/web/public/images/app/bitbucket.png +0 -0
  196. package/src/web/public/images/app/claude.png +0 -0
  197. package/src/web/public/images/app/cli.png +0 -0
  198. package/src/web/public/images/app/clickup.png +0 -0
  199. package/src/web/public/images/app/cohere.png +0 -0
  200. package/src/web/public/images/app/confluence.png +0 -0
  201. package/src/web/public/images/app/confluence2.png +0 -0
  202. package/src/web/public/images/app/curl.png +0 -0
  203. package/src/web/public/images/app/curl_mini.png +0 -0
  204. package/src/web/public/images/app/cursor.png +0 -0
  205. package/src/web/public/images/app/db2.png +0 -0
  206. package/src/web/public/images/app/deepseek.png +0 -0
  207. package/src/web/public/images/app/discord.png +0 -0
  208. package/src/web/public/images/app/docker.png +0 -0
  209. package/src/web/public/images/app/dockerhub.png +0 -0
  210. package/src/web/public/images/app/dropbox.png +0 -0
  211. package/src/web/public/images/app/elasticsearch.png +0 -0
  212. package/src/web/public/images/app/facebook.png +0 -0
  213. package/src/web/public/images/app/falai.png +0 -0
  214. package/src/web/public/images/app/fireworks.png +0 -0
  215. package/src/web/public/images/app/gdrive.png +0 -0
  216. package/src/web/public/images/app/gemini.png +0 -0
  217. package/src/web/public/images/app/github.png +0 -0
  218. package/src/web/public/images/app/githubcopilot.png +0 -0
  219. package/src/web/public/images/app/gitlab.png +0 -0
  220. package/src/web/public/images/app/gmail.png +0 -0
  221. package/src/web/public/images/app/googlecalender.png +0 -0
  222. package/src/web/public/images/app/googledocs.png +0 -0
  223. package/src/web/public/images/app/googlesheets.png +0 -0
  224. package/src/web/public/images/app/gradle.png +0 -0
  225. package/src/web/public/images/app/grafana.png +0 -0
  226. package/src/web/public/images/app/graphql.png +0 -0
  227. package/src/web/public/images/app/grok.png +0 -0
  228. package/src/web/public/images/app/groq.png +0 -0
  229. package/src/web/public/images/app/hazelcast.png +0 -0
  230. package/src/web/public/images/app/huggingface.png +0 -0
  231. package/src/web/public/images/app/imessage.png +0 -0
  232. package/src/web/public/images/app/instagram.png +0 -0
  233. package/src/web/public/images/app/intellij.png +0 -0
  234. package/src/web/public/images/app/jenkins.png +0 -0
  235. package/src/web/public/images/app/jira.png +0 -0
  236. package/src/web/public/images/app/kafka.png +0 -0
  237. package/src/web/public/images/app/kubernetes.png +0 -0
  238. package/src/web/public/images/app/linear.png +0 -0
  239. package/src/web/public/images/app/linkedin.png +0 -0
  240. package/src/web/public/images/app/llama.png +0 -0
  241. package/src/web/public/images/app/make.png +0 -0
  242. package/src/web/public/images/app/maven.png +0 -0
  243. package/src/web/public/images/app/mcp.png +0 -0
  244. package/src/web/public/images/app/microsoftteams.png +0 -0
  245. package/src/web/public/images/app/mistral.png +0 -0
  246. package/src/web/public/images/app/monday.png +0 -0
  247. package/src/web/public/images/app/mongodb.png +0 -0
  248. package/src/web/public/images/app/mssql.png +0 -0
  249. package/src/web/public/images/app/mysql.png +0 -0
  250. package/src/web/public/images/app/n8n.png +0 -0
  251. package/src/web/public/images/app/notion.png +0 -0
  252. package/src/web/public/images/app/npm.png +0 -0
  253. package/src/web/public/images/app/nuget.png +0 -0
  254. package/src/web/public/images/app/obsidian.png +0 -0
  255. package/src/web/public/images/app/openai.png +0 -0
  256. package/src/web/public/images/app/openrouter.png +0 -0
  257. package/src/web/public/images/app/opensearch.png +0 -0
  258. package/src/web/public/images/app/openshift.png +0 -0
  259. package/src/web/public/images/app/oracle.png +0 -0
  260. package/src/web/public/images/app/perplexity.png +0 -0
  261. package/src/web/public/images/app/pipedream.png +0 -0
  262. package/src/web/public/images/app/postgresql.png +0 -0
  263. package/src/web/public/images/app/powershell.png +0 -0
  264. package/src/web/public/images/app/prometheus.png +0 -0
  265. package/src/web/public/images/app/reddit.png +0 -0
  266. package/src/web/public/images/app/redis.png +0 -0
  267. package/src/web/public/images/app/rss.png +0 -0
  268. package/src/web/public/images/app/signal.png +0 -0
  269. package/src/web/public/images/app/slack.png +0 -0
  270. package/src/web/public/images/app/soap.png +0 -0
  271. package/src/web/public/images/app/sqlite.png +0 -0
  272. package/src/web/public/images/app/supabase.png +0 -0
  273. package/src/web/public/images/app/telegram.png +0 -0
  274. package/src/web/public/images/app/things3.png +0 -0
  275. package/src/web/public/images/app/threads.png +0 -0
  276. package/src/web/public/images/app/tiktok.png +0 -0
  277. package/src/web/public/images/app/together.png +0 -0
  278. package/src/web/public/images/app/trello.png +0 -0
  279. package/src/web/public/images/app/vscode.png +0 -0
  280. package/src/web/public/images/app/webhook.png +0 -0
  281. package/src/web/public/images/app/webpage.png +0 -0
  282. package/src/web/public/images/app/whatsappbusiness.png +0 -0
  283. package/src/web/public/images/app/windsorf.png +0 -0
  284. package/src/web/public/images/app/x.png +0 -0
  285. package/src/web/public/images/app/youtube.png +0 -0
  286. package/src/web/public/images/app/zapier.png +0 -0
  287. package/src/web/public/images/app/zededitor.png +0 -0
  288. package/src/web/public/images/app/zoom.png +0 -0
  289. package/src/web/public/images/avatar-anon.svg +4 -0
  290. package/src/web/public/images/favicon.png +0 -0
  291. package/src/web/public/images/install/chatgpt-web/step0.png +0 -0
  292. package/src/web/public/images/install/chatgpt-web/step1.png +0 -0
  293. package/src/web/public/images/install/chatgpt-web/step2.png +0 -0
  294. package/src/web/public/images/install/chatgpt-web/step3.png +0 -0
  295. package/src/web/public/images/install/chatgpt-web/step4.png +0 -0
  296. package/src/web/public/images/install/chatgpt-web/step5.png +0 -0
  297. package/src/web/public/images/readme/1-generate-servers.png +0 -0
  298. package/src/web/public/images/readme/2-database-connection.png +0 -0
  299. package/src/web/public/images/readme/2-file-upload.png +0 -0
  300. package/src/web/public/images/readme/3-data-preview.png +0 -0
  301. package/src/web/public/images/readme/4-data-preview2.png +0 -0
  302. package/src/web/public/images/readme/5-server-configuration.png +0 -0
  303. package/src/web/public/images/readme/6-server-generated-modal.png +0 -0
  304. package/src/web/public/images/readme/7-generated-servers.png +0 -0
  305. package/src/web/public/images/readme/8-generated-servers-view-details.png +0 -0
  306. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.23.51.png +0 -0
  307. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.24.59.png +0 -0
  308. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.25.05.png +0 -0
  309. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.25.14.png +0 -0
  310. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.25.21.png +0 -0
  311. package/src/web/public/images/readme/Screenshot 2025-10-09 at 20.25.36.png +0 -0
  312. package/src/web/public/index.html +4685 -488
  313. package/src/web/public/landing.html +1638 -0
  314. package/src/web/public/logger.js +31 -0
  315. package/src/web/public/login.html +372 -0
  316. package/src/web/public/manage-servers.html +121 -188
  317. package/src/web/public/pricing.html +537 -0
  318. package/src/web/public/quick-ask.html +133 -0
  319. package/src/web/public/quickmcp-styles.css +708 -0
  320. package/src/web/public/roles.html +177 -0
  321. package/src/web/public/shared.js +736 -3
  322. package/src/web/public/sidebar.js +414 -0
  323. package/src/web/public/test-servers.html +605 -221
  324. package/src/web/public/users.html +191 -0
  325. package/dist/client/MCPClientUnified.d.ts +0 -31
  326. package/dist/client/MCPClientUnified.d.ts.map +0 -1
  327. package/dist/client/MCPClientUnified.js +0 -275
  328. package/dist/client/MCPClientUnified.js.map +0 -1
  329. package/dist/client/MCPTestRunnerUnified.d.ts +0 -48
  330. package/dist/client/MCPTestRunnerUnified.d.ts.map +0 -1
  331. package/dist/client/MCPTestRunnerUnified.js +0 -183
  332. package/dist/client/MCPTestRunnerUnified.js.map +0 -1
  333. package/dist/database/json-manager.d.ts +0 -55
  334. package/dist/database/json-manager.d.ts.map +0 -1
  335. package/dist/database/json-manager.js +0 -128
  336. package/dist/database/json-manager.js.map +0 -1
  337. package/dist/dynamic-mcp-executor.d.ts.map +0 -1
  338. package/dist/dynamic-mcp-executor.js +0 -274
  339. package/dist/dynamic-mcp-executor.js.map +0 -1
  340. package/dist/generators/MCPServerGenerator-new.d.ts +0 -37
  341. package/dist/generators/MCPServerGenerator-new.d.ts.map +0 -1
  342. package/dist/generators/MCPServerGenerator-new.js +0 -287
  343. package/dist/generators/MCPServerGenerator-new.js.map +0 -1
  344. package/dist/generators/database/sqlite-manager.d.ts +0 -52
  345. package/dist/generators/database/sqlite-manager.js +0 -143
  346. package/dist/generators/generators/MCPServerGenerator.d.ts +0 -37
  347. package/dist/generators/generators/MCPServerGenerator.js +0 -396
  348. package/dist/integrated-mcp-server.d.ts +0 -25
  349. package/dist/integrated-mcp-server.d.ts.map +0 -1
  350. package/dist/integrated-mcp-server.js +0 -541
  351. package/dist/integrated-mcp-server.js.map +0 -1
  352. package/dist/mcp-inspector-server.d.ts +0 -3
  353. package/dist/mcp-inspector-server.d.ts.map +0 -1
  354. package/dist/mcp-inspector-server.js +0 -119
  355. package/dist/mcp-inspector-server.js.map +0 -1
  356. package/dist/mcp-sdk-server.d.ts +0 -3
  357. package/dist/mcp-sdk-server.d.ts.map +0 -1
  358. package/dist/mcp-sdk-server.js +0 -90
  359. package/dist/mcp-sdk-server.js.map +0 -1
  360. package/dist/mcp-server.d.ts +0 -3
  361. package/dist/mcp-server.d.ts.map +0 -1
  362. package/dist/mcp-server.js +0 -300
  363. package/dist/mcp-server.js.map +0 -1
  364. package/dist/parsers/parsers/ExcelParser.js +0 -118
  365. package/dist/quickmcp-unified-bridge.d.ts +0 -13
  366. package/dist/quickmcp-unified-bridge.d.ts.map +0 -1
  367. package/dist/quickmcp-unified-bridge.js +0 -176
  368. package/dist/quickmcp-unified-bridge.js.map +0 -1
  369. package/dist/sqlite-manager.js +0 -145
  370. package/dist/test-app.d.ts +0 -2
  371. package/dist/test-app.d.ts.map +0 -1
  372. package/dist/test-app.js +0 -119
  373. package/dist/test-app.js.map +0 -1
  374. package/dist/transport/base-transport.d.ts +0 -21
  375. package/dist/transport/base-transport.d.ts.map +0 -1
  376. package/dist/transport/base-transport.js +0 -16
  377. package/dist/transport/base-transport.js.map +0 -1
  378. package/dist/transport/index.d.ts +0 -10
  379. package/dist/transport/index.d.ts.map +0 -1
  380. package/dist/transport/index.js +0 -12
  381. package/dist/transport/index.js.map +0 -1
  382. package/dist/transport/sse-transport.d.ts +0 -13
  383. package/dist/transport/sse-transport.d.ts.map +0 -1
  384. package/dist/transport/sse-transport.js +0 -106
  385. package/dist/transport/sse-transport.js.map +0 -1
  386. package/dist/transport/stdio-transport.d.ts +0 -8
  387. package/dist/transport/stdio-transport.d.ts.map +0 -1
  388. package/dist/transport/stdio-transport.js +0 -53
  389. package/dist/transport/stdio-transport.js.map +0 -1
  390. package/dist/transport/streamable-http-transport.d.ts +0 -15
  391. package/dist/transport/streamable-http-transport.d.ts.map +0 -1
  392. package/dist/transport/streamable-http-transport.js +0 -151
  393. package/dist/transport/streamable-http-transport.js.map +0 -1
  394. package/dist/web/client/MCPClient.js +0 -348
  395. package/dist/web/client/MCPTestRunner.js +0 -317
  396. package/dist/web/database/json-manager.js +0 -124
  397. package/dist/web/database/sqlite-manager.js +0 -146
  398. package/dist/web/dynamic-mcp-executor.js +0 -443
  399. package/dist/web/generators/MCPServerGenerator-new.js +0 -284
  400. package/dist/web/generators/MCPServerGenerator.js +0 -566
  401. package/dist/web/integrated-mcp-server-new.js +0 -394
  402. package/dist/web/parsers/CsvParser.js +0 -144
  403. package/dist/web/parsers/DatabaseParser.js +0 -637
  404. package/dist/web/parsers/ExcelParser.js +0 -180
  405. package/dist/web/parsers/index.js +0 -152
  406. package/dist/web/server.d.ts.map +0 -1
  407. package/dist/web/server.js +0 -790
  408. package/dist/web/server.js.map +0 -1
  409. package/dist/web/types/index.js +0 -2
  410. package/dist/web/web/server.js +0 -860
  411. package/src/web/public/modern-styles.css +0 -946
  412. package/src/web/public/shared-styles.css +0 -2091
  413. /package/src/web/public/images/{1-claude-quickmcp-stdio.png → readme/1-claude-quickmcp-stdio.png} +0 -0
  414. /package/src/web/public/images/{2-claude-tools.png → readme/2-claude-tools.png} +0 -0
  415. /package/src/web/public/images/{3-claude-developer-settings.png → readme/3-claude-developer-settings.png} +0 -0
  416. /package/src/web/public/images/{4-claude-config.png → readme/4-claude-config.png} +0 -0
  417. /package/src/web/public/images/{5-claude-config-edit.png → readme/5-claude-config-edit.png} +0 -0
@@ -0,0 +1,1472 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AuthApi = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const token_utils_1 = require("../../auth/token-utils");
10
+ const jwks_provider_1 = require("../../auth/jwks-provider");
11
+ const logger_1 = require("../../utils/logger");
12
+ class AuthApi {
13
+ constructor(deps) {
14
+ this.deps = deps;
15
+ this.oauthRequestCookieName = 'quickmcp_oauth_req';
16
+ this.oauthNextCookieName = 'quickmcp_oauth_next';
17
+ this.oauthCodeTtlMs = 5 * 60 * 1000;
18
+ this.oauthAccessTokenTtlSec = Number(process.env.QUICKMCP_OAUTH_ACCESS_TTL_SEC || '3600');
19
+ this.oauthClients = new Map();
20
+ this.getJwks = (_req, res) => {
21
+ res.setHeader('Cache-Control', 'public, max-age=3600');
22
+ res.json((0, jwks_provider_1.getJwks)());
23
+ };
24
+ this.getOAuthAuthorizationServerMetadata = (req, res) => {
25
+ const issuer = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
26
+ res.json({
27
+ issuer,
28
+ authorization_endpoint: `${issuer}/oauth/authorize`,
29
+ token_endpoint: `${issuer}/oauth/token`,
30
+ introspection_endpoint: `${issuer}/oauth/introspect`,
31
+ userinfo_endpoint: `${issuer}/oauth/userinfo`,
32
+ registration_endpoint: `${issuer}/oauth/register`,
33
+ jwks_uri: `${issuer}/oauth/jwks`,
34
+ response_types_supported: ['code'],
35
+ response_modes_supported: ['query'],
36
+ grant_types_supported: ['authorization_code', 'refresh_token'],
37
+ code_challenge_methods_supported: ['S256'],
38
+ scopes_supported: ['openid', 'profile', 'email', 'offline_access', 'mcp'],
39
+ token_endpoint_auth_methods_supported: ['none', 'client_secret_post', 'client_secret_basic']
40
+ });
41
+ };
42
+ this.getOAuthProtectedResourceMetadata = (req, res) => {
43
+ const issuer = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
44
+ res.json({
45
+ resource: `${issuer}/mcp`,
46
+ authorization_servers: [issuer],
47
+ scopes_supported: ['openid', 'profile', 'email', 'offline_access', 'mcp'],
48
+ jwks_uri: `${issuer}/oauth/jwks`,
49
+ bearer_methods_supported: ['header', 'body'],
50
+ resource_documentation: 'https://www.quickmcp.ai/docs',
51
+ resource_registration: `${issuer}/oauth/register`
52
+ });
53
+ };
54
+ this.getOpenIdConfigurationMetadata = (req, res) => {
55
+ const issuer = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
56
+ res.json({
57
+ issuer,
58
+ authorization_endpoint: `${issuer}/oauth/authorize`,
59
+ token_endpoint: `${issuer}/oauth/token`,
60
+ introspection_endpoint: `${issuer}/oauth/introspect`,
61
+ userinfo_endpoint: `${issuer}/oauth/userinfo`,
62
+ registration_endpoint: `${issuer}/oauth/register`,
63
+ jwks_uri: `${issuer}/oauth/jwks`,
64
+ response_types_supported: ['code'],
65
+ response_modes_supported: ['query'],
66
+ grant_types_supported: ['authorization_code', 'refresh_token'],
67
+ code_challenge_methods_supported: ['S256'],
68
+ scopes_supported: ['openid', 'profile', 'email', 'offline_access', 'mcp'],
69
+ token_endpoint_auth_methods_supported: ['none', 'client_secret_post', 'client_secret_basic'],
70
+ request_uri_parameter_supported: false,
71
+ require_request_uri_registration: false,
72
+ request_parameter_supported: false,
73
+ authorization_response_iss_parameter_supported: true
74
+ });
75
+ };
76
+ this.oauthRegister = (req, res) => {
77
+ const body = (req.body && typeof req.body === 'object') ? req.body : {};
78
+ const redirectUrisRaw = body.redirect_uris;
79
+ const redirectUris = Array.isArray(redirectUrisRaw)
80
+ ? redirectUrisRaw.map((v) => String(v || '').trim()).filter(Boolean)
81
+ : [];
82
+ const clientName = String(body.client_name || 'QuickMCP ChatGPT Client').trim();
83
+ if (redirectUris.length === 0) {
84
+ res.status(400).json({ error: 'invalid_client_metadata', error_description: 'redirect_uris is required' });
85
+ return;
86
+ }
87
+ if (redirectUris.some((uri) => !/^https?:\/\//i.test(uri))) {
88
+ res.status(400).json({ error: 'invalid_redirect_uri', error_description: 'redirect_uris must be absolute URLs' });
89
+ return;
90
+ }
91
+ const clientId = `quickmcp_${crypto_1.default.randomBytes(16).toString('hex')}`;
92
+ const createdAt = Math.floor(Date.now() / 1000);
93
+ this.oauthClients.set(clientId, {
94
+ clientId,
95
+ redirectUris,
96
+ clientName,
97
+ tokenEndpointAuthMethod: 'none',
98
+ createdAt
99
+ });
100
+ res.status(201).json({
101
+ client_id: clientId,
102
+ client_id_issued_at: createdAt,
103
+ client_name: clientName,
104
+ redirect_uris: redirectUris,
105
+ grant_types: ['authorization_code', 'refresh_token'],
106
+ response_types: ['code'],
107
+ token_endpoint_auth_method: 'none'
108
+ });
109
+ };
110
+ this.oauthAuthorize = (req, res) => {
111
+ const responseType = String(req.query.response_type || '').trim();
112
+ const clientId = String(req.query.client_id || 'chatgpt').trim();
113
+ const redirectUri = String(req.query.redirect_uri || '').trim();
114
+ const state = String(req.query.state || '').trim();
115
+ const scope = String(req.query.scope || '').trim();
116
+ const canonicalResource = `${this.resolveAppBaseUrl(req).replace(/\/+$/, '')}/mcp`;
117
+ const requestedResource = String(req.query.resource || '').trim();
118
+ // Force canonical resource host to prevent quickmcp.ai vs www.quickmcp.ai token-binding drift.
119
+ const resource = canonicalResource;
120
+ const codeChallenge = String(req.query.code_challenge || '').trim();
121
+ const codeChallengeMethod = this.normalizeOAuthCodeChallengeMethod(String(req.query.code_challenge_method || 'S256'));
122
+ const nonce = String(req.query.nonce || '').trim() || undefined;
123
+ if (responseType !== 'code') {
124
+ if (redirectUri) {
125
+ this.redirectOAuthError(res, redirectUri, state, 'unsupported_response_type', 'response_type must be code');
126
+ }
127
+ else {
128
+ res.status(400).json({ error: 'unsupported_response_type', error_description: 'response_type must be code' });
129
+ }
130
+ return;
131
+ }
132
+ if (!redirectUri) {
133
+ res.status(400).json({ error: 'invalid_request', error_description: 'redirect_uri is required' });
134
+ return;
135
+ }
136
+ if (clientId) {
137
+ const registeredClient = this.oauthClients.get(clientId);
138
+ if (registeredClient && !registeredClient.redirectUris.includes(redirectUri)) {
139
+ this.redirectOAuthError(res, redirectUri, state, 'invalid_request', 'redirect_uri is not registered for client_id');
140
+ return;
141
+ }
142
+ }
143
+ if (!codeChallenge) {
144
+ this.redirectOAuthError(res, redirectUri, state, 'invalid_request', 'code_challenge is required');
145
+ return;
146
+ }
147
+ const pending = {
148
+ clientId,
149
+ redirectUri,
150
+ state,
151
+ scope,
152
+ resource,
153
+ codeChallenge,
154
+ codeChallengeMethod,
155
+ nonce,
156
+ createdAt: Date.now()
157
+ };
158
+ if (requestedResource && requestedResource !== resource) {
159
+ logger_1.logger.info(`[oauth/authorize] normalize resource requested=${requestedResource} canonical=${resource}`);
160
+ }
161
+ const encoded = this.encodeOAuthPendingRequest(pending);
162
+ this.deps.setCookie(res, this.oauthRequestCookieName, encoded, 10 * 60);
163
+ res.redirect('/oauth/authorize/complete');
164
+ };
165
+ this.oauthAuthorizeComplete = async (req, res) => {
166
+ const cookies = this.deps.parseCookies(req);
167
+ const rawPending = cookies[this.oauthRequestCookieName];
168
+ const pending = this.decodeOAuthPendingRequest(rawPending || '');
169
+ if (!pending || !pending.redirectUri) {
170
+ res.status(400).json({ error: 'invalid_request', error_description: 'Missing or invalid OAuth authorization request' });
171
+ return;
172
+ }
173
+ if (Date.now() - pending.createdAt > 10 * 60 * 1000) {
174
+ this.deps.clearCookie(res, this.oauthRequestCookieName);
175
+ this.redirectOAuthError(res, pending.redirectUri, pending.state, 'invalid_request', 'OAuth authorization request expired');
176
+ return;
177
+ }
178
+ const ctx = await this.deps.resolveAuthContext(req, res);
179
+ if (!ctx) {
180
+ const next = '/oauth/authorize/complete';
181
+ res.redirect(`/login?next=${encodeURIComponent(next)}`);
182
+ return;
183
+ }
184
+ const code = this.createAuthorizationCode({
185
+ clientId: pending.clientId,
186
+ redirectUri: pending.redirectUri,
187
+ scope: pending.scope,
188
+ resource: pending.resource,
189
+ codeChallenge: pending.codeChallenge,
190
+ codeChallengeMethod: pending.codeChallengeMethod,
191
+ nonce: pending.nonce,
192
+ username: ctx.username,
193
+ workspaceId: ctx.workspaceId,
194
+ role: ctx.role
195
+ });
196
+ this.deps.clearCookie(res, this.oauthRequestCookieName);
197
+ let location = this.appendQueryParam(pending.redirectUri, 'code', code);
198
+ if (pending.state)
199
+ location = this.appendQueryParam(location, 'state', pending.state);
200
+ res.redirect(location);
201
+ };
202
+ this.oauthToken = async (req, res) => {
203
+ const body = (req.body && typeof req.body === 'object') ? req.body : {};
204
+ const grantType = String(body.grant_type || '').trim();
205
+ const code = String(body.code || '').trim();
206
+ const redirectUri = String(body.redirect_uri || '').trim();
207
+ const clientId = String(body.client_id || '').trim();
208
+ const codeVerifier = String(body.code_verifier || '').trim();
209
+ const requestId = crypto_1.default.randomBytes(6).toString('hex');
210
+ logger_1.logger.info(`[oauth/token:${requestId}] headers=${JSON.stringify(req.headers)}`);
211
+ logger_1.logger.info(`[oauth/token:${requestId}] body=${JSON.stringify(body)}`);
212
+ logger_1.logger.info(`[oauth/token:${requestId}] request grant_type=${grantType || '(empty)'} client_id=${clientId || '(empty)'}`);
213
+ // refresh_token grant: look up the token and issue a new access token
214
+ if (grantType === 'refresh_token') {
215
+ const refreshTokenValue = String(body.refresh_token || '').trim();
216
+ if (!refreshTokenValue) {
217
+ res.status(400).json({ error: 'invalid_request', error_description: 'refresh_token is required' });
218
+ return;
219
+ }
220
+ try {
221
+ const store = this.deps.ensureDataStore();
222
+ const refreshHash = crypto_1.default.createHash('sha256').update(refreshTokenValue).digest('hex');
223
+ const existingToken = await store.getMcpTokenByHash(refreshHash);
224
+ if (!existingToken || existingToken.revokedAt) {
225
+ logger_1.logger.warn(`[oauth/token:${requestId}] refresh_token invalid or revoked`);
226
+ res.status(400).json({ error: 'invalid_grant', error_description: 'refresh_token is invalid or revoked' });
227
+ return;
228
+ }
229
+ const refreshTtlSec = Number.isFinite(this.oauthAccessTokenTtlSec) && this.oauthAccessTokenTtlSec > 0
230
+ ? Math.floor(this.oauthAccessTokenTtlSec)
231
+ : 3600;
232
+ const resourceUrl = `${this.resolveAppBaseUrl(req).replace(/\/+$/, '')}/mcp`;
233
+ const issuerUrl = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
234
+ const newTokenPack = this.deps.createMcpToken(existingToken.subjectUsername, existingToken.workspaceId, this.deps.getUserRole(existingToken.subjectUsername, existingToken.workspaceId), refreshTtlSec, resourceUrl, issuerUrl, 'mcp');
235
+ await store.createMcpToken({
236
+ id: newTokenPack.tokenId,
237
+ tokenName: `oauth:refresh:${clientId || 'chatgpt'}`,
238
+ workspaceId: existingToken.workspaceId,
239
+ subjectUsername: existingToken.subjectUsername,
240
+ createdBy: existingToken.subjectUsername,
241
+ tokenHash: newTokenPack.tokenHash,
242
+ tokenValue: newTokenPack.token,
243
+ allowAllServers: true,
244
+ allowAllTools: true,
245
+ allowAllResources: true,
246
+ serverIds: [],
247
+ allowedTools: [],
248
+ allowedResources: [],
249
+ serverRules: {},
250
+ toolRules: {},
251
+ resourceRules: {},
252
+ neverExpires: false,
253
+ expiresAt: newTokenPack.expiresAt
254
+ });
255
+ const newRefreshToken = crypto_1.default.randomBytes(48).toString('base64url');
256
+ const newRefreshHash = crypto_1.default.createHash('sha256').update(newRefreshToken).digest('hex');
257
+ await store.createMcpToken({
258
+ id: crypto_1.default.randomUUID(),
259
+ tokenName: `oauth:refresh-token:${clientId || 'chatgpt'}`,
260
+ workspaceId: existingToken.workspaceId,
261
+ subjectUsername: existingToken.subjectUsername,
262
+ createdBy: existingToken.subjectUsername,
263
+ tokenHash: newRefreshHash,
264
+ tokenValue: newRefreshToken,
265
+ allowAllServers: false,
266
+ allowAllTools: false,
267
+ allowAllResources: false,
268
+ serverIds: [],
269
+ allowedTools: [],
270
+ allowedResources: [],
271
+ serverRules: {},
272
+ toolRules: {},
273
+ resourceRules: {},
274
+ neverExpires: true,
275
+ expiresAt: null
276
+ });
277
+ res.setHeader('Cache-Control', 'no-store');
278
+ res.setHeader('Pragma', 'no-cache');
279
+ logger_1.logger.info(`[oauth/token:${requestId}] refresh success user=${existingToken.subjectUsername}`);
280
+ const refreshIdToken = this.createIdToken({ sub: existingToken.subjectUsername, issuer: issuerUrl, clientId: clientId || 'chatgpt', ttlSec: refreshTtlSec });
281
+ const refreshResponse = {
282
+ access_token: newTokenPack.token,
283
+ token_type: 'Bearer',
284
+ expires_in: refreshTtlSec,
285
+ refresh_token: newRefreshToken,
286
+ id_token: refreshIdToken,
287
+ scope: 'mcp openid',
288
+ resource: resourceUrl
289
+ };
290
+ logger_1.logger.info(`[oauth/token:${requestId}] refresh response token_type=${refreshResponse.token_type} scope="${refreshResponse.scope}" resource="${refreshResponse.resource}" access_token=${this.maskTokenForLog(refreshResponse.access_token)} refresh_token=${this.maskTokenForLog(refreshResponse.refresh_token)} id_token=${this.maskTokenForLog(refreshResponse.id_token)}`);
291
+ res.json(refreshResponse);
292
+ }
293
+ catch (err) {
294
+ logger_1.logger.warn(`[oauth/token:${requestId}] refresh_token error: ${err instanceof Error ? err.message : String(err)}`);
295
+ res.status(400).json({ error: 'invalid_grant', error_description: 'refresh_token processing failed' });
296
+ }
297
+ return;
298
+ }
299
+ if (grantType !== 'authorization_code') {
300
+ logger_1.logger.warn(`[oauth/token:${requestId}] rejected unsupported grant_type`);
301
+ res.status(400).json({ error: 'unsupported_grant_type', error_description: 'grant_type must be authorization_code or refresh_token' });
302
+ return;
303
+ }
304
+ if (!code) {
305
+ logger_1.logger.warn(`[oauth/token:${requestId}] rejected missing code`);
306
+ res.status(400).json({ error: 'invalid_request', error_description: 'code is required' });
307
+ return;
308
+ }
309
+ const record = this.decodeOAuthCode(code);
310
+ if (!record || record.expiresAt <= Date.now()) {
311
+ logger_1.logger.warn(`[oauth/token:${requestId}] invalid or expired code`);
312
+ res.status(400).json({ error: 'invalid_grant', error_description: 'authorization code is invalid or expired' });
313
+ return;
314
+ }
315
+ if (redirectUri && this.normalizeRedirectUri(record.redirectUri) !== this.normalizeRedirectUri(redirectUri)) {
316
+ logger_1.logger.warn(`[oauth/token:${requestId}] redirect mismatch expected=${record.redirectUri} got=${redirectUri}`);
317
+ res.status(400).json({ error: 'invalid_grant', error_description: 'redirect_uri does not match authorization request' });
318
+ return;
319
+ }
320
+ if (clientId && record.clientId && record.clientId !== clientId) {
321
+ // Some clients can rotate/re-register IDs between registration and callback retries.
322
+ // Do not hard-fail here; rely on redirect_uri + PKCE for validation.
323
+ logger_1.logger.warn(`[oauth/token:${requestId}] client_id mismatch expected=${record.clientId} got=${clientId}`);
324
+ }
325
+ if (record.codeChallenge) {
326
+ if (!codeVerifier) {
327
+ logger_1.logger.warn(`[oauth/token:${requestId}] missing code_verifier`);
328
+ res.status(400).json({ error: 'invalid_request', error_description: 'code_verifier is required' });
329
+ return;
330
+ }
331
+ const computed = record.codeChallengeMethod === 'plain'
332
+ ? codeVerifier
333
+ : crypto_1.default.createHash('sha256').update(codeVerifier).digest('base64url');
334
+ if (computed !== record.codeChallenge) {
335
+ logger_1.logger.warn(`[oauth/token:${requestId}] invalid code_verifier`);
336
+ res.status(400).json({ error: 'invalid_grant', error_description: 'code_verifier is invalid' });
337
+ return;
338
+ }
339
+ }
340
+ const ttlSec = Number.isFinite(this.oauthAccessTokenTtlSec) && this.oauthAccessTokenTtlSec > 0
341
+ ? Math.floor(this.oauthAccessTokenTtlSec)
342
+ : 3600;
343
+ const resourceUrl = record.resource || `${this.resolveAppBaseUrl(req).replace(/\/+$/, '')}/mcp`;
344
+ const issuerUrl = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
345
+ const tokenPack = this.deps.createMcpToken(record.username, record.workspaceId, record.role, ttlSec, resourceUrl, issuerUrl, record.scope || 'mcp');
346
+ // Generate a refresh token (stored as a special token record in the DB)
347
+ const refreshToken = crypto_1.default.randomBytes(48).toString('base64url');
348
+ const refreshTokenHash = crypto_1.default.createHash('sha256').update(refreshToken).digest('hex');
349
+ try {
350
+ const store = this.deps.ensureDataStore();
351
+ await store.createMcpToken({
352
+ id: tokenPack.tokenId,
353
+ tokenName: `oauth:${clientId || 'chatgpt'}`,
354
+ workspaceId: record.workspaceId,
355
+ subjectUsername: record.username,
356
+ createdBy: record.username,
357
+ tokenHash: tokenPack.tokenHash,
358
+ tokenValue: tokenPack.token,
359
+ allowAllServers: true,
360
+ allowAllTools: true,
361
+ allowAllResources: true,
362
+ serverIds: [],
363
+ allowedTools: [],
364
+ allowedResources: [],
365
+ serverRules: {},
366
+ toolRules: {},
367
+ resourceRules: {},
368
+ neverExpires: false,
369
+ expiresAt: tokenPack.expiresAt
370
+ });
371
+ // Store refresh token as a long-lived record so it can be looked up later
372
+ await store.createMcpToken({
373
+ id: crypto_1.default.randomUUID(),
374
+ tokenName: `oauth:refresh-token:${clientId || 'chatgpt'}`,
375
+ workspaceId: record.workspaceId,
376
+ subjectUsername: record.username,
377
+ createdBy: record.username,
378
+ tokenHash: refreshTokenHash,
379
+ tokenValue: refreshToken,
380
+ allowAllServers: false,
381
+ allowAllTools: false,
382
+ allowAllResources: false,
383
+ serverIds: [],
384
+ allowedTools: [],
385
+ allowedResources: [],
386
+ serverRules: {},
387
+ toolRules: {},
388
+ resourceRules: {},
389
+ neverExpires: true,
390
+ expiresAt: null
391
+ });
392
+ }
393
+ catch (err) {
394
+ logger_1.logger.warn(`[oauth/token:${requestId}] failed to persist token: ${err instanceof Error ? err.message : String(err)}`);
395
+ }
396
+ res.setHeader('Cache-Control', 'no-store');
397
+ res.setHeader('Pragma', 'no-cache');
398
+ logger_1.logger.info(`[oauth/token:${requestId}] success user=${record.username} ws=${record.workspaceId}`);
399
+ const grantScopes = (record.scope || '').split(/\s+/);
400
+ const idToken = grantScopes.includes('openid')
401
+ ? this.createIdToken({ sub: record.username, issuer: issuerUrl, clientId: clientId || record.clientId, ttlSec, nonce: record.nonce })
402
+ : undefined;
403
+ const responseScope = [...new Set([...grantScopes, 'openid'])].join(' ');
404
+ const tokenResponse = {
405
+ access_token: tokenPack.token,
406
+ token_type: 'Bearer',
407
+ expires_in: ttlSec,
408
+ refresh_token: refreshToken,
409
+ ...(idToken ? { id_token: idToken } : {}),
410
+ scope: responseScope,
411
+ resource: resourceUrl
412
+ };
413
+ logger_1.logger.info(`[oauth/token:${requestId}] response token_type=${tokenResponse.token_type} scope="${tokenResponse.scope}" resource="${tokenResponse.resource}" access_token=${this.maskTokenForLog(tokenResponse.access_token)} refresh_token=${this.maskTokenForLog(tokenResponse.refresh_token)} id_token=${this.maskTokenForLog(tokenResponse.id_token)}`);
414
+ res.json(tokenResponse);
415
+ };
416
+ // RFC 7662 Token Introspection
417
+ this.oauthIntrospect = async (req, res) => {
418
+ const body = (req.body && typeof req.body === 'object') ? req.body : {};
419
+ const token = String(body.token || '').trim();
420
+ res.setHeader('Cache-Control', 'no-store');
421
+ if (!token) {
422
+ res.json({ active: false });
423
+ return;
424
+ }
425
+ try {
426
+ const tokenSecret = String(process.env.QUICKMCP_TOKEN_SECRET || process.env.AUTH_COOKIE_SECRET || 'change-me');
427
+ const rsaPublicKey = (0, jwks_provider_1.getRsaPublicKey)();
428
+ const payload = (rsaPublicKey ? (0, token_utils_1.verifyMcpTokenRS256)(token, rsaPublicKey) : null)
429
+ ?? (0, token_utils_1.verifyMcpToken)(token, tokenSecret);
430
+ if (!payload) {
431
+ res.json({ active: false });
432
+ return;
433
+ }
434
+ const issuer = this.resolveAppBaseUrl(req).replace(/\/+$/, '');
435
+ res.json({
436
+ active: true,
437
+ sub: payload.sub,
438
+ scope: payload.scope || 'mcp openid',
439
+ username: payload.sub,
440
+ token_type: 'Bearer',
441
+ exp: payload.exp,
442
+ iat: payload.iat,
443
+ iss: payload.iss || issuer,
444
+ aud: payload.aud,
445
+ jti: payload.jti
446
+ });
447
+ }
448
+ catch {
449
+ res.json({ active: false });
450
+ }
451
+ };
452
+ // OIDC UserInfo endpoint (RFC 7517)
453
+ this.oauthUserinfo = async (req, res) => {
454
+ const authHeader = String(req.headers.authorization || '').trim();
455
+ const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
456
+ const token = bearerMatch ? bearerMatch[1].trim() : '';
457
+ if (!token) {
458
+ res.setHeader('WWW-Authenticate', 'Bearer error="invalid_token"');
459
+ res.status(401).json({ error: 'invalid_token', error_description: 'Bearer token required' });
460
+ return;
461
+ }
462
+ try {
463
+ const tokenSecret = String(process.env.QUICKMCP_TOKEN_SECRET || process.env.AUTH_COOKIE_SECRET || 'change-me');
464
+ const rsaPublicKey = (0, jwks_provider_1.getRsaPublicKey)();
465
+ const payload = (rsaPublicKey ? (0, token_utils_1.verifyMcpTokenRS256)(token, rsaPublicKey) : null)
466
+ ?? (0, token_utils_1.verifyMcpToken)(token, tokenSecret);
467
+ if (!payload) {
468
+ res.setHeader('WWW-Authenticate', 'Bearer error="invalid_token"');
469
+ res.status(401).json({ error: 'invalid_token', error_description: 'Token is invalid or expired' });
470
+ return;
471
+ }
472
+ res.json({
473
+ sub: payload.sub,
474
+ name: payload.sub,
475
+ preferred_username: payload.sub,
476
+ profile: `${this.resolveAppBaseUrl(req).replace(/\/+$/, '')}/users/${encodeURIComponent(payload.sub)}`
477
+ });
478
+ }
479
+ catch {
480
+ res.status(500).json({ error: 'server_error' });
481
+ }
482
+ };
483
+ this.getMe = async (req, res) => {
484
+ const ctx = await this.deps.resolveAuthContext(req, res);
485
+ if (!ctx) {
486
+ res.status(401).json({ success: false, error: 'Unauthorized' });
487
+ return;
488
+ }
489
+ const cookies = this.deps.parseCookies(req);
490
+ const accessToken = cookies[this.deps.accessCookieName];
491
+ const payload = accessToken
492
+ ? ((0, token_utils_1.verifyAccessToken)(accessToken, this.deps.authCookieSecret)
493
+ || (0, token_utils_1.verifyAccessTokenAllowExpired)(accessToken, this.deps.authCookieSecret))
494
+ : null;
495
+ let storeUser = null;
496
+ try {
497
+ storeUser = await this.deps.ensureDataStore().getUser(ctx.username);
498
+ }
499
+ catch { }
500
+ const createdDate = payload?.createdDate || storeUser?.createdAt || '';
501
+ const lastSignInDate = payload?.lastSignInDate || (payload?.iat ? new Date(payload.iat * 1000).toISOString() : '');
502
+ res.json({
503
+ success: true,
504
+ data: {
505
+ username: ctx.username,
506
+ displayName: payload?.displayName || ctx.username,
507
+ workspaceId: ctx.workspaceId,
508
+ authMode: this.deps.authMode,
509
+ email: payload?.email || '',
510
+ avatarUrl: payload?.avatarUrl || '',
511
+ createdDate,
512
+ lastSignInDate,
513
+ role: ctx.role,
514
+ isAdmin: ctx.role === 'admin'
515
+ }
516
+ });
517
+ };
518
+ this.getRoles = async (req, res) => {
519
+ const ctx = await this.deps.resolveAuthContext(req, res);
520
+ if (!ctx) {
521
+ res.status(401).json({ success: false, error: 'Unauthorized' });
522
+ return;
523
+ }
524
+ res.json({
525
+ success: true,
526
+ data: {
527
+ roles: [
528
+ { id: 'admin', name: 'Admin', description: 'Can manage users and all servers.' },
529
+ { id: 'user', name: 'User', description: 'Can use the app and manage own servers.' }
530
+ ],
531
+ currentUser: { username: ctx.username, workspaceId: ctx.workspaceId, role: ctx.role, isAdmin: ctx.role === 'admin' }
532
+ }
533
+ });
534
+ };
535
+ this.getUsers = async (req, res) => {
536
+ if (this.isSaasMode()) {
537
+ res.status(404).json({ success: false, error: 'Users API is not available in SAAS mode' });
538
+ return;
539
+ }
540
+ const actor = await this.deps.requireAdminApi(req, res);
541
+ if (!actor)
542
+ return;
543
+ try {
544
+ const users = await this.deps.ensureDataStore().getAllUsersByWorkspace(actor.workspaceId);
545
+ res.json({ success: true, data: { users, requestedBy: actor.username, workspaceId: actor.workspaceId } });
546
+ }
547
+ catch (error) {
548
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to load users' });
549
+ }
550
+ };
551
+ this.getAuthorizationConfig = async (req, res) => {
552
+ const ctx = await this.deps.resolveAuthContext(req, res);
553
+ if (!ctx) {
554
+ res.status(401).json({ success: false, error: 'Unauthorized' });
555
+ return;
556
+ }
557
+ const isSaasMode = this.isSaasMode();
558
+ res.json({
559
+ success: true,
560
+ data: {
561
+ authMode: this.deps.authMode,
562
+ deployMode: String(this.deps.deployMode || '').trim().toUpperCase(),
563
+ isSaasMode,
564
+ mcpTokenRequired: this.deps.authMode !== 'NONE',
565
+ username: ctx.username,
566
+ workspaceId: ctx.workspaceId
567
+ }
568
+ });
569
+ };
570
+ this.getAuthorizationContext = async (req, res) => {
571
+ const ctx = await this.deps.resolveAuthContext(req, res);
572
+ if (!ctx) {
573
+ res.status(401).json({ success: false, error: 'Unauthorized' });
574
+ return;
575
+ }
576
+ try {
577
+ const store = this.deps.ensureDataStore();
578
+ const workspaceUsers = await store.getAllUsersByWorkspace(ctx.workspaceId);
579
+ const canManageWorkspace = ctx.role === 'admin' || this.isSaasMode();
580
+ const users = (canManageWorkspace
581
+ ? workspaceUsers
582
+ : workspaceUsers.filter((u) => u.username === ctx.username)).map((u) => ({ username: u.username, role: u.role }));
583
+ const serverRows = await store.getAllServersByOwner(ctx.workspaceId);
584
+ const servers = await Promise.all(serverRows.map(async (server) => {
585
+ const tools = (await store.getToolsForServer(server.id)).map((t) => `${server.id}__${t.name}`);
586
+ const resources = (await store.getResourcesForServer(server.id)).map((r) => `${server.id}__${r.name}`);
587
+ const rawType = server.sourceConfig?.type;
588
+ const type = typeof rawType === 'string' ? rawType : 'unknown';
589
+ return { id: server.id, name: server.name, type, tools, resources };
590
+ }));
591
+ res.json({ success: true, data: { workspaceId: ctx.workspaceId, users, servers } });
592
+ }
593
+ catch (error) {
594
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to load authorization context' });
595
+ }
596
+ };
597
+ this.getAuthorizationServers = async (req, res) => {
598
+ const ctx = await this.deps.resolveAuthContext(req, res);
599
+ if (!ctx) {
600
+ res.status(401).json({ success: false, error: 'Unauthorized' });
601
+ return;
602
+ }
603
+ try {
604
+ const store = this.deps.ensureDataStore();
605
+ const servers = await store.getAllServersByOwner(ctx.workspaceId);
606
+ const data = await Promise.all(servers.map(async (server) => {
607
+ const cfg = await store.getServerAuthConfig(server.id);
608
+ return {
609
+ id: server.id,
610
+ name: server.name,
611
+ ownerUsername: server.ownerUsername,
612
+ requireMcpToken: cfg ? cfg.requireMcpToken : true
613
+ };
614
+ }));
615
+ res.json({ success: true, data: { servers: data } });
616
+ }
617
+ catch (error) {
618
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to load authorization settings' });
619
+ }
620
+ };
621
+ this.getAuthorizationTokenPolicy = async (req, res) => {
622
+ const actor = this.isSaasMode()
623
+ ? await this.deps.resolveAuthContext(req, res)
624
+ : await this.deps.requireAdminApi(req, res);
625
+ if (this.isSaasMode() && !actor) {
626
+ res.status(401).json({ success: false, error: 'Unauthorized' });
627
+ return;
628
+ }
629
+ if (!actor)
630
+ return;
631
+ try {
632
+ const store = this.deps.ensureDataStore();
633
+ const users = (await store.getAllUsersByWorkspace(actor.workspaceId)).map((u) => u.username);
634
+ const servers = (await store.getAllServersByOwner(actor.workspaceId)).map((s) => s.id);
635
+ const toolsByServer = await Promise.all(servers.map((serverId) => store.getToolsForServer(serverId)));
636
+ const tools = servers.flatMap((serverId, index) => toolsByServer[index].map((t) => `${serverId}__${t.name}`));
637
+ const globalPolicy = await store.getMcpTokenPolicy('global', '*');
638
+ const userPolicies = await store.listMcpTokenPolicies('user');
639
+ const serverPolicies = await store.listMcpTokenPolicies('server');
640
+ const toolPolicies = await store.listMcpTokenPolicies('tool');
641
+ const userRules = {};
642
+ const serverRules = {};
643
+ const toolRules = {};
644
+ for (const username of users)
645
+ userRules[username] = null;
646
+ for (const id of servers)
647
+ serverRules[id] = null;
648
+ for (const id of tools)
649
+ toolRules[id] = null;
650
+ userPolicies
651
+ .filter((p) => users.includes(p.scopeId))
652
+ .forEach((p) => { userRules[p.scopeId] = p.requireMcpToken; });
653
+ serverPolicies
654
+ .filter((p) => p.scopeId.startsWith(`${actor.workspaceId}__`))
655
+ .forEach((p) => { serverRules[p.scopeId] = p.requireMcpToken; });
656
+ toolPolicies
657
+ .filter((p) => p.scopeId.startsWith(`${actor.workspaceId}__`))
658
+ .forEach((p) => { toolRules[p.scopeId] = p.requireMcpToken; });
659
+ res.json({
660
+ success: true,
661
+ data: {
662
+ globalRequireMcpToken: globalPolicy ? globalPolicy.requireMcpToken : true,
663
+ userRules,
664
+ serverRules,
665
+ toolRules
666
+ }
667
+ });
668
+ }
669
+ catch (error) {
670
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to load token policy' });
671
+ }
672
+ };
673
+ this.updateAuthorizationTokenPolicy = async (req, res) => {
674
+ const actor = this.isSaasMode()
675
+ ? await this.deps.resolveAuthContext(req, res)
676
+ : await this.deps.requireAdminApi(req, res);
677
+ if (this.isSaasMode() && !actor) {
678
+ res.status(401).json({ success: false, error: 'Unauthorized' });
679
+ return;
680
+ }
681
+ if (!actor)
682
+ return;
683
+ try {
684
+ const store = this.deps.ensureDataStore();
685
+ const users = (await store.getAllUsersByWorkspace(actor.workspaceId)).map((u) => u.username);
686
+ const usersSet = new Set(users);
687
+ const globalRequireMcpToken = req.body?.globalRequireMcpToken !== false;
688
+ const userRules = this.normalizePolicyMap(req.body?.userRules, 'user', actor.workspaceId, usersSet);
689
+ const serverRules = this.normalizePolicyMap(req.body?.serverRules, 'server', actor.workspaceId, usersSet);
690
+ const toolRules = this.normalizePolicyMap(req.body?.toolRules, 'tool', actor.workspaceId, usersSet);
691
+ await store.setMcpTokenPolicy('global', '*', globalRequireMcpToken);
692
+ await this.applyPolicyMap(store, 'user', userRules);
693
+ await this.applyPolicyMap(store, 'server', serverRules);
694
+ await this.applyPolicyMap(store, 'tool', toolRules);
695
+ res.json({
696
+ success: true,
697
+ data: {
698
+ globalRequireMcpToken,
699
+ userRules,
700
+ serverRules,
701
+ toolRules
702
+ }
703
+ });
704
+ }
705
+ catch (error) {
706
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to update token policy' });
707
+ }
708
+ };
709
+ this.updateAuthorizationServer = async (req, res) => {
710
+ const ctx = await this.deps.resolveAuthContext(req, res);
711
+ if (!ctx) {
712
+ res.status(401).json({ success: false, error: 'Unauthorized' });
713
+ return;
714
+ }
715
+ const serverId = String(req.params.id || '').trim();
716
+ const requireMcpToken = req.body?.requireMcpToken !== false;
717
+ if (!serverId) {
718
+ res.status(400).json({ success: false, error: 'Server id is required' });
719
+ return;
720
+ }
721
+ try {
722
+ const store = this.deps.ensureDataStore();
723
+ const server = await store.getServerForOwner(serverId, ctx.workspaceId);
724
+ if (!server) {
725
+ res.status(404).json({ success: false, error: 'Server not found for current user' });
726
+ return;
727
+ }
728
+ await store.setServerAuthConfig(serverId, requireMcpToken);
729
+ res.json({ success: true, data: { serverId, requireMcpToken } });
730
+ }
731
+ catch (error) {
732
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to update authorization settings' });
733
+ }
734
+ };
735
+ this.createAuthorizationMcpToken = async (req, res) => {
736
+ const ctx = await this.deps.resolveAuthContext(req, res);
737
+ if (!ctx) {
738
+ res.status(401).json({ success: false, error: 'Unauthorized' });
739
+ return;
740
+ }
741
+ const store = this.deps.ensureDataStore();
742
+ const tokenName = String(req.body?.tokenName || '').trim();
743
+ if (!tokenName) {
744
+ res.status(400).json({ success: false, error: 'Token name is required' });
745
+ return;
746
+ }
747
+ const subjectUsername = this.deps.normalizeUsername(req.body?.subjectUsername) || ctx.username;
748
+ if (subjectUsername !== ctx.username && (this.isSaasMode() || ctx.role !== 'admin')) {
749
+ res.status(403).json({ success: false, error: 'Only admin can generate token for other users' });
750
+ return;
751
+ }
752
+ const subjectUser = await store.getUserInWorkspace(subjectUsername, ctx.workspaceId);
753
+ if (!subjectUser) {
754
+ res.status(404).json({ success: false, error: 'Selected user is not in this workspace' });
755
+ return;
756
+ }
757
+ const neverExpires = req.body?.neverExpires === true;
758
+ const rawTtlHours = Number(req.body?.ttlHours);
759
+ const ttlHours = neverExpires ? null : (Number.isFinite(rawTtlHours) && rawTtlHours > 0 ? Math.min(rawTtlHours, 24 * 3650) : 24 * 30);
760
+ const serverRules = this.normalizeTriStateRules(req.body?.serverRules, ctx.workspaceId, 'server');
761
+ const toolRules = this.normalizeTriStateRules(req.body?.toolRules, ctx.workspaceId, 'tool');
762
+ const resourceRules = this.normalizeTriStateRules(req.body?.resourceRules, ctx.workspaceId, 'resource');
763
+ // Backward-compatible fields derived from tri-state maps.
764
+ const serverAllows = Object.entries(serverRules).filter(([, v]) => v === true).map(([k]) => k);
765
+ const serverDenies = Object.entries(serverRules).filter(([, v]) => v === false).map(([k]) => k);
766
+ const toolAllows = Object.entries(toolRules).filter(([, v]) => v === true).map(([k]) => k);
767
+ const toolDenies = Object.entries(toolRules).filter(([, v]) => v === false).map(([k]) => k);
768
+ const resourceAllows = Object.entries(resourceRules).filter(([, v]) => v === true).map(([k]) => k);
769
+ const resourceDenies = Object.entries(resourceRules).filter(([, v]) => v === false).map(([k]) => k);
770
+ const allowAllServers = serverAllows.length === 0 && serverDenies.length === 0;
771
+ const allowAllTools = toolAllows.length === 0 && toolDenies.length === 0;
772
+ const allowAllResources = resourceAllows.length === 0 && resourceDenies.length === 0;
773
+ const serverIds = serverAllows;
774
+ const allowedTools = toolAllows;
775
+ const allowedResources = resourceAllows;
776
+ const tokenPack = this.deps.createMcpToken(subjectUser.username, subjectUser.workspaceId, subjectUser.role, ttlHours ? ttlHours * 3600 : undefined);
777
+ await store.createMcpToken({
778
+ id: tokenPack.tokenId,
779
+ tokenName,
780
+ workspaceId: subjectUser.workspaceId,
781
+ subjectUsername: subjectUser.username,
782
+ createdBy: ctx.username,
783
+ tokenHash: tokenPack.tokenHash,
784
+ tokenValue: tokenPack.token,
785
+ allowAllServers,
786
+ allowAllTools,
787
+ allowAllResources,
788
+ serverIds,
789
+ allowedTools,
790
+ allowedResources,
791
+ serverRules,
792
+ toolRules,
793
+ resourceRules,
794
+ neverExpires,
795
+ expiresAt: tokenPack.expiresAt
796
+ });
797
+ res.json({
798
+ success: true,
799
+ data: {
800
+ id: tokenPack.tokenId,
801
+ tokenName,
802
+ token: tokenPack.token,
803
+ expiresAt: tokenPack.expiresAt,
804
+ neverExpires,
805
+ subjectUsername: subjectUser.username,
806
+ workspaceId: subjectUser.workspaceId
807
+ }
808
+ });
809
+ };
810
+ this.getAuthorizationTokens = async (req, res) => {
811
+ const ctx = await this.deps.resolveAuthContext(req, res);
812
+ if (!ctx) {
813
+ res.status(401).json({ success: false, error: 'Unauthorized' });
814
+ return;
815
+ }
816
+ const canManageWorkspace = ctx.role === 'admin' || this.isSaasMode();
817
+ const tokens = (await this.deps.ensureDataStore()
818
+ .getMcpTokensByWorkspace(ctx.workspaceId))
819
+ .filter((t) => canManageWorkspace || t.subjectUsername === ctx.username)
820
+ .map((t) => ({
821
+ id: t.id,
822
+ tokenName: t.tokenName,
823
+ subjectUsername: t.subjectUsername,
824
+ createdBy: t.createdBy,
825
+ createdAt: t.createdAt,
826
+ expiresAt: t.expiresAt,
827
+ neverExpires: t.neverExpires,
828
+ revokedAt: t.revokedAt,
829
+ allowAllServers: t.allowAllServers,
830
+ allowAllTools: t.allowAllTools,
831
+ allowAllResources: t.allowAllResources,
832
+ serverRules: t.serverRules || {},
833
+ toolRules: t.toolRules || {},
834
+ resourceRules: t.resourceRules || {}
835
+ }));
836
+ res.json({ success: true, data: { tokens } });
837
+ };
838
+ this.getAuthorizationTokenById = async (req, res) => {
839
+ const ctx = await this.deps.resolveAuthContext(req, res);
840
+ if (!ctx) {
841
+ res.status(401).json({ success: false, error: 'Unauthorized' });
842
+ return;
843
+ }
844
+ const canManageWorkspace = ctx.role === 'admin' || this.isSaasMode();
845
+ const token = await this.deps.ensureDataStore().getMcpTokenById(String(req.params.id || ''));
846
+ if (!token || token.workspaceId !== ctx.workspaceId || (!canManageWorkspace && token.subjectUsername !== ctx.username)) {
847
+ res.status(404).json({ success: false, error: 'Token not found' });
848
+ return;
849
+ }
850
+ res.json({
851
+ success: true,
852
+ data: {
853
+ id: token.id,
854
+ tokenName: token.tokenName,
855
+ token: token.tokenValue,
856
+ subjectUsername: token.subjectUsername,
857
+ createdBy: token.createdBy,
858
+ createdAt: token.createdAt,
859
+ expiresAt: token.expiresAt,
860
+ neverExpires: token.neverExpires,
861
+ revokedAt: token.revokedAt,
862
+ allowAllServers: token.allowAllServers,
863
+ allowAllTools: token.allowAllTools,
864
+ allowAllResources: token.allowAllResources,
865
+ serverRules: token.serverRules || {},
866
+ toolRules: token.toolRules || {},
867
+ resourceRules: token.resourceRules || {}
868
+ }
869
+ });
870
+ };
871
+ this.deleteAuthorizationToken = async (req, res) => {
872
+ const ctx = await this.deps.resolveAuthContext(req, res);
873
+ if (!ctx) {
874
+ res.status(401).json({ success: false, error: 'Unauthorized' });
875
+ return;
876
+ }
877
+ const canManageWorkspace = ctx.role === 'admin' || this.isSaasMode();
878
+ const token = await this.deps.ensureDataStore().getMcpTokenById(String(req.params.id || ''));
879
+ if (!token || token.workspaceId !== ctx.workspaceId || (!canManageWorkspace && token.subjectUsername !== ctx.username)) {
880
+ res.status(404).json({ success: false, error: 'Token not found' });
881
+ return;
882
+ }
883
+ await this.deps.ensureDataStore().revokeMcpToken(token.id);
884
+ res.json({ success: true });
885
+ };
886
+ this.createUser = async (req, res) => {
887
+ if (this.isSaasMode()) {
888
+ res.status(404).json({ success: false, error: 'User management is not available in SAAS mode' });
889
+ return;
890
+ }
891
+ const actor = await this.deps.requireAdminApi(req, res);
892
+ if (!actor)
893
+ return;
894
+ const username = this.deps.normalizeUsername(req.body?.username);
895
+ const password = typeof req.body?.password === 'string' ? req.body.password : '';
896
+ const roleInput = typeof req.body?.role === 'string' ? req.body.role.trim().toLowerCase() : 'user';
897
+ const role = roleInput === 'admin' ? 'admin' : 'user';
898
+ if (!username) {
899
+ res.status(400).json({ success: false, error: 'Username is required' });
900
+ return;
901
+ }
902
+ if (!/^[a-zA-Z0-9._-]{3,64}$/.test(username)) {
903
+ res.status(400).json({ success: false, error: 'Username must be 3-64 chars and contain only letters, numbers, dot, underscore, or dash' });
904
+ return;
905
+ }
906
+ if (!password || password.length < 6) {
907
+ res.status(400).json({ success: false, error: 'Password must be at least 6 characters' });
908
+ return;
909
+ }
910
+ try {
911
+ const store = this.deps.ensureDataStore();
912
+ if (await store.getUserInWorkspace(username, actor.workspaceId)) {
913
+ res.status(409).json({ success: false, error: `User "${username}" already exists` });
914
+ return;
915
+ }
916
+ await store.createUser(username, this.deps.hashPassword(password), role, actor.workspaceId);
917
+ res.status(201).json({
918
+ success: true,
919
+ data: {
920
+ user: { username, role, workspaceId: actor.workspaceId },
921
+ createdBy: actor.username
922
+ }
923
+ });
924
+ }
925
+ catch (error) {
926
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to create user' });
927
+ }
928
+ };
929
+ this.updateUserRole = async (req, res) => {
930
+ if (this.isSaasMode()) {
931
+ res.status(404).json({ success: false, error: 'User management is not available in SAAS mode' });
932
+ return;
933
+ }
934
+ const actor = await this.deps.requireAdminApi(req, res);
935
+ if (!actor)
936
+ return;
937
+ const targetUsername = this.deps.normalizeUsername(req.params.username);
938
+ const roleInput = typeof req.body?.role === 'string' ? req.body.role.trim().toLowerCase() : '';
939
+ const role = roleInput === 'admin' ? 'admin' : 'user';
940
+ if (!targetUsername) {
941
+ res.status(400).json({ success: false, error: 'Username is required' });
942
+ return;
943
+ }
944
+ const store = this.deps.ensureDataStore();
945
+ const target = await store.getUserInWorkspace(targetUsername, actor.workspaceId);
946
+ if (!target) {
947
+ res.status(404).json({ success: false, error: 'User not found in current workspace' });
948
+ return;
949
+ }
950
+ await store.updateUserRole(targetUsername, actor.workspaceId, role);
951
+ res.json({ success: true, data: { user: { username: targetUsername, workspaceId: actor.workspaceId, role } } });
952
+ };
953
+ this.login = async (req, res) => {
954
+ if (this.deps.authMode === 'NONE') {
955
+ res.status(400).json({ success: false, error: 'Login is disabled when AUTH_MODE=NONE' });
956
+ return;
957
+ }
958
+ if (this.deps.authMode === 'SUPABASE_GOOGLE') {
959
+ res.status(400).json({ success: false, error: 'Use Google Sign-In for SUPABASE_GOOGLE mode' });
960
+ return;
961
+ }
962
+ const username = this.deps.normalizeUsername(req.body?.username);
963
+ const password = typeof req.body?.password === 'string' ? req.body.password : '';
964
+ if (!username || !password) {
965
+ res.status(400).json({ success: false, error: 'Username and password are required' });
966
+ return;
967
+ }
968
+ let authenticatedUsername = '';
969
+ let authenticatedWorkspaceId = '';
970
+ let authenticatedRole = 'user';
971
+ try {
972
+ const storeUser = await this.deps.ensureDataStore().getUser(username);
973
+ if (storeUser && this.deps.verifyPassword(password, storeUser.passwordHash)) {
974
+ authenticatedUsername = storeUser.username;
975
+ authenticatedWorkspaceId = storeUser.workspaceId || storeUser.username;
976
+ authenticatedRole = storeUser.role;
977
+ }
978
+ else {
979
+ const legacyAdmin = this.deps.authAdminUsers.find((item) => item.username === username && item.password === password);
980
+ if (legacyAdmin) {
981
+ authenticatedUsername = legacyAdmin.username;
982
+ authenticatedWorkspaceId = legacyAdmin.username;
983
+ authenticatedRole = 'admin';
984
+ await this.deps.ensureDataStore().upsertUser(legacyAdmin.username, this.deps.hashPassword(legacyAdmin.password), 'admin', legacyAdmin.username);
985
+ }
986
+ }
987
+ }
988
+ catch (error) {
989
+ logger_1.logger.error('Login datastore error:', error);
990
+ }
991
+ if (!authenticatedUsername) {
992
+ res.status(401).json({ success: false, error: 'Invalid username or password' });
993
+ return;
994
+ }
995
+ if (!authenticatedWorkspaceId)
996
+ authenticatedWorkspaceId = authenticatedUsername;
997
+ await this.issueSession(res, authenticatedUsername, authenticatedWorkspaceId, authenticatedRole, {
998
+ displayName: authenticatedUsername
999
+ });
1000
+ res.json({
1001
+ success: true,
1002
+ data: {
1003
+ username: authenticatedUsername,
1004
+ workspaceId: authenticatedWorkspaceId,
1005
+ role: authenticatedRole,
1006
+ isAdmin: authenticatedRole === 'admin'
1007
+ }
1008
+ });
1009
+ };
1010
+ this.oauthProviderStart = (req, res) => {
1011
+ const authProperty = this.deps.getAuthProperty();
1012
+ if (this.deps.authMode !== 'SUPABASE_GOOGLE') {
1013
+ res.status(400).json({ success: false, error: 'Supabase login is only available when AUTH_MODE=SUPABASE_GOOGLE' });
1014
+ return;
1015
+ }
1016
+ if (!authProperty.providerUrl) {
1017
+ res.status(500).json({ success: false, error: 'SUPABASE_URL is not configured' });
1018
+ return;
1019
+ }
1020
+ const next = typeof req.query.next === 'string' && req.query.next.startsWith('/') ? req.query.next : '/';
1021
+ const appBaseUrl = this.resolveAppBaseUrl(req);
1022
+ const redirectTo = `${appBaseUrl.replace(/\/+$/, '')}/login?oauth=callback`;
1023
+ this.deps.setCookie(res, this.oauthNextCookieName, next, 10 * 60);
1024
+ const authorizeUrl = `${authProperty.providerUrl.replace(/\/+$/, '')}/auth/v1/authorize?provider=google&redirect_to=${encodeURIComponent(redirectTo)}`;
1025
+ res.redirect(authorizeUrl);
1026
+ };
1027
+ this.oauthProviderSession = async (req, res) => {
1028
+ const authProperty = this.deps.getAuthProperty();
1029
+ if (this.deps.authMode !== 'SUPABASE_GOOGLE') {
1030
+ res.status(400).json({ success: false, error: 'Supabase session endpoint is only available when AUTH_MODE=SUPABASE_GOOGLE' });
1031
+ return;
1032
+ }
1033
+ if (!authProperty.providerUrl || !authProperty.publicKey) {
1034
+ res.status(500).json({ success: false, error: 'SUPABASE_URL or SUPABASE_ANON_KEY is not configured' });
1035
+ return;
1036
+ }
1037
+ const cookies = this.deps.parseCookies(req);
1038
+ const cookieNextRaw = String(cookies[this.oauthNextCookieName] || '').trim();
1039
+ const cookieNext = cookieNextRaw.startsWith('/') ? cookieNextRaw : '/oauth/authorize/complete';
1040
+ const accessToken = typeof req.body?.accessToken === 'string' ? req.body.accessToken.trim() : '';
1041
+ if (!accessToken) {
1042
+ res.status(400).json({ success: false, error: 'accessToken is required' });
1043
+ return;
1044
+ }
1045
+ try {
1046
+ const userRes = await fetch(`${authProperty.providerUrl.replace(/\/+$/, '')}/auth/v1/user`, {
1047
+ headers: {
1048
+ apikey: authProperty.publicKey,
1049
+ Authorization: `Bearer ${accessToken}`
1050
+ }
1051
+ });
1052
+ const userPayload = await userRes.json().catch(() => ({}));
1053
+ if (!userRes.ok || !userPayload?.id) {
1054
+ res.status(401).json({ success: false, error: userPayload?.msg || userPayload?.error_description || 'Invalid Supabase access token' });
1055
+ return;
1056
+ }
1057
+ const email = typeof userPayload?.email === 'string' ? userPayload.email : '';
1058
+ const avatarUrl = typeof userPayload?.user_metadata?.avatar_url === 'string'
1059
+ ? userPayload.user_metadata.avatar_url
1060
+ : '';
1061
+ const displayNameRaw = userPayload?.user_metadata?.full_name
1062
+ || userPayload?.user_metadata?.name
1063
+ || userPayload?.user_metadata?.preferred_username
1064
+ || '';
1065
+ const displayName = String(displayNameRaw || '').trim();
1066
+ const createdDate = typeof userPayload?.created_at === 'string'
1067
+ ? userPayload.created_at
1068
+ : (typeof userPayload?.createdAt === 'string'
1069
+ ? userPayload.createdAt
1070
+ : (typeof userPayload?.identities?.[0]?.created_at === 'string'
1071
+ ? userPayload.identities[0].created_at
1072
+ : ''));
1073
+ const lastSignInDate = typeof userPayload?.last_sign_in_at === 'string'
1074
+ ? userPayload.last_sign_in_at
1075
+ : (typeof userPayload?.lastSignInAt === 'string'
1076
+ ? userPayload.lastSignInAt
1077
+ : (typeof userPayload?.identities?.[0]?.last_sign_in_at === 'string'
1078
+ ? userPayload.identities[0].last_sign_in_at
1079
+ : new Date().toISOString()));
1080
+ const username = this.normalizeSupabaseUsername(email, userPayload?.id);
1081
+ const workspaceId = username;
1082
+ const role = this.resolveSupabaseRole(username, email);
1083
+ await this.deps.ensureDataStore().upsertUser(username, this.deps.hashPassword(`supabase:${userPayload.id}`), role, workspaceId);
1084
+ await this.issueSession(res, username, workspaceId, role, {
1085
+ displayName: displayName || username,
1086
+ email,
1087
+ avatarUrl,
1088
+ createdDate,
1089
+ lastSignInDate
1090
+ });
1091
+ this.deps.clearCookie(res, this.oauthNextCookieName);
1092
+ res.json({
1093
+ success: true,
1094
+ data: {
1095
+ username,
1096
+ workspaceId,
1097
+ role,
1098
+ isAdmin: role === 'admin',
1099
+ next: cookieNext
1100
+ }
1101
+ });
1102
+ }
1103
+ catch (error) {
1104
+ res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Failed to validate Supabase session' });
1105
+ }
1106
+ };
1107
+ this.refresh = async (req, res) => {
1108
+ if (this.deps.authMode === 'NONE') {
1109
+ res.status(400).json({ success: false, error: 'Refresh endpoint is not available when AUTH_MODE=NONE' });
1110
+ return;
1111
+ }
1112
+ const cookies = this.deps.parseCookies(req);
1113
+ const refreshToken = cookies[this.deps.refreshCookieName];
1114
+ const currentAccessToken = cookies[this.deps.accessCookieName];
1115
+ const currentPayload = currentAccessToken
1116
+ ? ((0, token_utils_1.verifyAccessToken)(currentAccessToken, this.deps.authCookieSecret)
1117
+ || (0, token_utils_1.verifyAccessTokenAllowExpired)(currentAccessToken, this.deps.authCookieSecret))
1118
+ : null;
1119
+ if (!refreshToken) {
1120
+ res.status(401).json({ success: false, error: 'Missing refresh token' });
1121
+ return;
1122
+ }
1123
+ const refreshTokenHash = (0, token_utils_1.hashRefreshToken)(refreshToken, this.deps.authCookieSecret);
1124
+ const record = await this.deps.ensureDataStore().getRefreshToken(refreshTokenHash);
1125
+ if (!record || record.revokedAt || new Date(record.expiresAt).getTime() <= Date.now()) {
1126
+ if (record && !record.revokedAt)
1127
+ await this.deps.ensureDataStore().revokeRefreshToken(refreshTokenHash);
1128
+ this.deps.clearCookie(res, this.deps.accessCookieName);
1129
+ this.deps.clearCookie(res, this.deps.refreshCookieName);
1130
+ res.status(401).json({ success: false, error: 'Refresh token is invalid or expired' });
1131
+ return;
1132
+ }
1133
+ await this.deps.ensureDataStore().revokeRefreshToken(refreshTokenHash);
1134
+ const newRefreshToken = (0, token_utils_1.createRefreshToken)();
1135
+ const newRefreshHash = (0, token_utils_1.hashRefreshToken)(newRefreshToken, this.deps.authCookieSecret);
1136
+ const refreshExpiresAt = new Date(Date.now() + this.deps.authRefreshTtlSec * 1000).toISOString();
1137
+ await this.deps.ensureDataStore().saveRefreshToken(newRefreshHash, record.username, refreshExpiresAt);
1138
+ const refreshedUser = await this.deps.ensureDataStore().getUser(record.username);
1139
+ const refreshedWorkspaceId = refreshedUser?.workspaceId || record.username;
1140
+ const refreshedRole = (refreshedUser?.role || this.deps.getUserRole(record.username, refreshedWorkspaceId));
1141
+ const newAccessToken = (0, token_utils_1.createAccessToken)(record.username, this.deps.authCookieSecret, this.deps.authAccessTtlSec, refreshedWorkspaceId, refreshedRole, currentPayload?.displayName || record.username, currentPayload?.email, currentPayload?.avatarUrl, currentPayload?.createdDate, currentPayload?.lastSignInDate);
1142
+ this.deps.setCookie(res, this.deps.accessCookieName, newAccessToken, this.deps.authAccessTtlSec);
1143
+ this.deps.setCookie(res, this.deps.refreshCookieName, newRefreshToken, this.deps.authRefreshTtlSec);
1144
+ res.json({ success: true, data: { username: record.username } });
1145
+ };
1146
+ this.logout = async (req, res) => {
1147
+ const cookies = this.deps.parseCookies(req);
1148
+ const refreshToken = cookies[this.deps.refreshCookieName];
1149
+ if (refreshToken) {
1150
+ const refreshTokenHash = (0, token_utils_1.hashRefreshToken)(refreshToken, this.deps.authCookieSecret);
1151
+ await this.deps.ensureDataStore().revokeRefreshToken(refreshTokenHash);
1152
+ }
1153
+ this.deps.clearCookie(res, this.deps.accessCookieName);
1154
+ this.deps.clearCookie(res, this.deps.refreshCookieName);
1155
+ res.json({ success: true });
1156
+ };
1157
+ this.getLoginPage = async (req, res) => {
1158
+ if (this.deps.authMode === 'NONE') {
1159
+ res.redirect('/');
1160
+ return;
1161
+ }
1162
+ const next = typeof req.query.next === 'string' && req.query.next.startsWith('/')
1163
+ ? req.query.next
1164
+ : '/';
1165
+ const ctx = await this.deps.resolveAuthContext(req, res);
1166
+ if (ctx) {
1167
+ res.redirect(next);
1168
+ return;
1169
+ }
1170
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
1171
+ res.setHeader('Pragma', 'no-cache');
1172
+ res.setHeader('Expires', '0');
1173
+ res.setHeader('Surrogate-Control', 'no-store');
1174
+ res.sendFile(path_1.default.join(this.deps.publicDir, 'login.html'));
1175
+ };
1176
+ this.getUsersPage = (_req, res) => {
1177
+ if (this.isSaasMode()) {
1178
+ res.status(404).send('Not Found');
1179
+ return;
1180
+ }
1181
+ res.sendFile(path_1.default.join(this.deps.publicDir, 'users.html'));
1182
+ };
1183
+ this.getAuthorizationPage = (_req, res) => {
1184
+ res.sendFile(path_1.default.join(this.deps.publicDir, 'authorization.html'));
1185
+ };
1186
+ }
1187
+ maskTokenForLog(value) {
1188
+ const token = String(value || '');
1189
+ if (!token)
1190
+ return '';
1191
+ if (token.length <= 16)
1192
+ return `${token.slice(0, 4)}...(${token.length})`;
1193
+ return `${token.slice(0, 8)}...${token.slice(-6)}(${token.length})`;
1194
+ }
1195
+ isSaasMode() {
1196
+ return String(this.deps.deployMode || '').trim().toUpperCase() === 'SAAS';
1197
+ }
1198
+ resolveAppBaseUrl(req) {
1199
+ const configured = this.deps.getAuthProperty().appBaseUrl.trim();
1200
+ const hostHeader = (req.get('x-forwarded-host') || req.get('host') || '').trim();
1201
+ const protoHeader = (req.get('x-forwarded-proto') || req.protocol || 'http').trim();
1202
+ const host = hostHeader.split(',')[0].trim().toLowerCase();
1203
+ const proto = protoHeader.split(',')[0].trim().toLowerCase() || 'http';
1204
+ const requestOrigin = host ? `${proto}://${host}` : '';
1205
+ if (!configured)
1206
+ return requestOrigin;
1207
+ // Use a single canonical issuer/resource in production to avoid token binding drift
1208
+ // between www/non-www hosts.
1209
+ try {
1210
+ const configuredHost = new URL(configured).host.toLowerCase();
1211
+ const isLocalConfigured = configuredHost.startsWith('localhost:') || configuredHost.startsWith('127.0.0.1:');
1212
+ if (!isLocalConfigured)
1213
+ return configured;
1214
+ }
1215
+ catch {
1216
+ return requestOrigin || configured;
1217
+ }
1218
+ // Local development convenience: if APP_BASE_URL is localhost but request host differs,
1219
+ // reflect request origin.
1220
+ if (requestOrigin)
1221
+ return requestOrigin;
1222
+ return configured;
1223
+ }
1224
+ normalizeTriStateRules(raw, workspaceId, prefix) {
1225
+ const input = raw && typeof raw === 'object' ? raw : {};
1226
+ const out = {};
1227
+ for (const [key, value] of Object.entries(input)) {
1228
+ const id = String(key || '').trim();
1229
+ if (!id || !id.startsWith(prefix === 'server' ? `${workspaceId}__` : `${workspaceId}__`))
1230
+ continue;
1231
+ if (value === true || value === 'allow' || value === 'true' || value === 1 || value === '1')
1232
+ out[id] = true;
1233
+ else if (value === false || value === 'deny' || value === 'false' || value === -1 || value === '-1')
1234
+ out[id] = false;
1235
+ else
1236
+ out[id] = null;
1237
+ }
1238
+ return out;
1239
+ }
1240
+ normalizePolicyMap(raw, kind, workspaceId, usersInWorkspace) {
1241
+ const input = raw && typeof raw === 'object' ? raw : {};
1242
+ const out = {};
1243
+ for (const [key, value] of Object.entries(input)) {
1244
+ const id = String(key || '').trim();
1245
+ if (!id)
1246
+ continue;
1247
+ if (kind === 'user' && !usersInWorkspace.has(id))
1248
+ continue;
1249
+ if ((kind === 'server' || kind === 'tool') && !id.startsWith(`${workspaceId}__`))
1250
+ continue;
1251
+ if (value === true || value === 'allow' || value === 'true' || value === 1 || value === '1')
1252
+ out[id] = true;
1253
+ else if (value === false || value === 'deny' || value === 'false' || value === 0 || value === '0')
1254
+ out[id] = false;
1255
+ else
1256
+ out[id] = null;
1257
+ }
1258
+ return out;
1259
+ }
1260
+ applyPolicyMap(store, scopeType, map) {
1261
+ const writes = [];
1262
+ for (const [id, decision] of Object.entries(map)) {
1263
+ writes.push(store.setMcpTokenPolicy(scopeType, id, decision));
1264
+ }
1265
+ return Promise.all(writes).then(() => undefined);
1266
+ }
1267
+ registerRoutes(app) {
1268
+ app.get('/.well-known/oauth-authorization-server', this.getOAuthAuthorizationServerMetadata);
1269
+ app.get('/.well-known/oauth-authorization-server/mcp', this.getOAuthAuthorizationServerMetadata);
1270
+ app.get('/mcp/.well-known/oauth-authorization-server', this.getOAuthAuthorizationServerMetadata);
1271
+ app.get('/.well-known/oauth-protected-resource', this.getOAuthProtectedResourceMetadata);
1272
+ app.get('/.well-known/oauth-protected-resource/mcp', this.getOAuthProtectedResourceMetadata);
1273
+ app.get('/mcp/.well-known/oauth-protected-resource', this.getOAuthProtectedResourceMetadata);
1274
+ app.get('/.well-known/openid-configuration', this.getOpenIdConfigurationMetadata);
1275
+ app.get('/.well-known/openid-configuration/mcp', this.getOpenIdConfigurationMetadata);
1276
+ app.get('/mcp/.well-known/openid-configuration', this.getOpenIdConfigurationMetadata);
1277
+ app.get('/oauth/jwks', this.getJwks);
1278
+ app.post('/oauth/register', this.oauthRegister);
1279
+ app.get('/oauth/authorize', this.oauthAuthorize);
1280
+ app.get('/oauth/authorize/complete', this.oauthAuthorizeComplete);
1281
+ app.post('/oauth/token', this.oauthToken);
1282
+ app.post('/oauth/introspect', this.oauthIntrospect);
1283
+ app.get('/oauth/userinfo', this.oauthUserinfo);
1284
+ app.post('/oauth/userinfo', this.oauthUserinfo);
1285
+ app.get('/api/auth/me', this.getMe);
1286
+ app.get('/api/auth/roles', this.getRoles);
1287
+ app.get('/api/auth/users', this.getUsers);
1288
+ app.get('/api/authorization/config', this.getAuthorizationConfig);
1289
+ app.get('/api/authorization/context', this.getAuthorizationContext);
1290
+ app.get('/api/authorization/token-policy', this.getAuthorizationTokenPolicy);
1291
+ app.post('/api/authorization/token-policy', this.updateAuthorizationTokenPolicy);
1292
+ app.get('/api/authorization/servers', this.getAuthorizationServers);
1293
+ app.post('/api/authorization/servers/:id', this.updateAuthorizationServer);
1294
+ app.post('/api/authorization/mcp-token', this.createAuthorizationMcpToken);
1295
+ app.get('/api/authorization/tokens', this.getAuthorizationTokens);
1296
+ app.get('/api/authorization/tokens/:id', this.getAuthorizationTokenById);
1297
+ app.delete('/api/authorization/tokens/:id', this.deleteAuthorizationToken);
1298
+ app.post('/api/auth/users', this.createUser);
1299
+ app.patch('/api/auth/users/:username/role', this.updateUserRole);
1300
+ app.post('/api/auth/login', this.login);
1301
+ app.get('/api/auth/oauth/start', this.oauthProviderStart);
1302
+ app.post('/api/auth/oauth/session', this.oauthProviderSession);
1303
+ // Backward-compatible aliases (deprecated).
1304
+ app.get('/api/auth/supabase/start', this.oauthProviderStart);
1305
+ app.post('/api/auth/supabase/session', this.oauthProviderSession);
1306
+ app.post('/api/auth/refresh', this.refresh);
1307
+ app.post('/api/auth/logout', this.logout);
1308
+ app.get('/login', this.getLoginPage);
1309
+ app.get('/users', this.getUsersPage);
1310
+ app.get('/authorization', this.getAuthorizationPage);
1311
+ }
1312
+ normalizeOAuthCodeChallengeMethod(value) {
1313
+ const normalized = String(value || '').trim().toUpperCase();
1314
+ return normalized === 'PLAIN' ? 'plain' : 'S256';
1315
+ }
1316
+ toBase64Url(input) {
1317
+ return Buffer.from(input, 'utf8').toString('base64url');
1318
+ }
1319
+ fromBase64Url(input) {
1320
+ return Buffer.from(input, 'base64url').toString('utf8');
1321
+ }
1322
+ signOAuthState(payloadEncoded) {
1323
+ return crypto_1.default.createHmac('sha256', this.deps.authCookieSecret).update(payloadEncoded).digest('base64url');
1324
+ }
1325
+ encodeOAuthPendingRequest(payload) {
1326
+ const encoded = this.toBase64Url(JSON.stringify(payload));
1327
+ const sig = this.signOAuthState(encoded);
1328
+ return `${encoded}.${sig}`;
1329
+ }
1330
+ decodeOAuthPendingRequest(raw) {
1331
+ const value = String(raw || '').trim();
1332
+ const dot = value.lastIndexOf('.');
1333
+ if (dot <= 0 || dot >= value.length - 1)
1334
+ return null;
1335
+ const encoded = value.slice(0, dot);
1336
+ const sig = value.slice(dot + 1);
1337
+ const expected = this.signOAuthState(encoded);
1338
+ if (sig !== expected)
1339
+ return null;
1340
+ try {
1341
+ const parsed = JSON.parse(this.fromBase64Url(encoded));
1342
+ if (!parsed || typeof parsed !== 'object')
1343
+ return null;
1344
+ return {
1345
+ clientId: String(parsed.clientId || ''),
1346
+ redirectUri: String(parsed.redirectUri || ''),
1347
+ state: String(parsed.state || ''),
1348
+ scope: String(parsed.scope || ''),
1349
+ resource: String(parsed.resource || ''),
1350
+ codeChallenge: String(parsed.codeChallenge || ''),
1351
+ codeChallengeMethod: this.normalizeOAuthCodeChallengeMethod(String(parsed.codeChallengeMethod || 'S256')),
1352
+ createdAt: Number(parsed.createdAt || 0)
1353
+ };
1354
+ }
1355
+ catch {
1356
+ return null;
1357
+ }
1358
+ }
1359
+ encodeOAuthCode(record) {
1360
+ const encoded = this.toBase64Url(JSON.stringify(record));
1361
+ const sig = this.signOAuthState(encoded);
1362
+ return `${encoded}.${sig}`;
1363
+ }
1364
+ decodeOAuthCode(code) {
1365
+ const value = String(code || '').trim();
1366
+ const dot = value.lastIndexOf('.');
1367
+ if (dot <= 0 || dot >= value.length - 1)
1368
+ return null;
1369
+ const encoded = value.slice(0, dot);
1370
+ const sig = value.slice(dot + 1);
1371
+ const expected = this.signOAuthState(encoded);
1372
+ if (sig !== expected)
1373
+ return null;
1374
+ try {
1375
+ const parsed = JSON.parse(this.fromBase64Url(encoded));
1376
+ if (!parsed || typeof parsed !== 'object')
1377
+ return null;
1378
+ return parsed;
1379
+ }
1380
+ catch {
1381
+ return null;
1382
+ }
1383
+ }
1384
+ createIdToken(params) {
1385
+ const now = Math.floor(Date.now() / 1000);
1386
+ const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: (0, jwks_provider_1.getKid)() })).toString('base64url');
1387
+ const payloadObj = {
1388
+ iss: params.issuer,
1389
+ sub: params.sub,
1390
+ aud: params.clientId,
1391
+ iat: now,
1392
+ exp: now + params.ttlSec
1393
+ };
1394
+ if (params.nonce)
1395
+ payloadObj.nonce = params.nonce;
1396
+ const payloadEncoded = Buffer.from(JSON.stringify(payloadObj)).toString('base64url');
1397
+ const signingInput = `${header}.${payloadEncoded}`;
1398
+ const signature = crypto_1.default.sign('sha256', Buffer.from(signingInput), (0, jwks_provider_1.getRsaPrivateKey)()).toString('base64url');
1399
+ return `${signingInput}.${signature}`;
1400
+ }
1401
+ createAuthorizationCode(record) {
1402
+ const payload = {
1403
+ ...record,
1404
+ expiresAt: Date.now() + this.oauthCodeTtlMs
1405
+ };
1406
+ return this.encodeOAuthCode(payload);
1407
+ }
1408
+ appendQueryParam(urlStr, key, value) {
1409
+ try {
1410
+ const url = new URL(urlStr);
1411
+ url.searchParams.set(key, value);
1412
+ return url.toString();
1413
+ }
1414
+ catch {
1415
+ const sep = urlStr.includes('?') ? '&' : '?';
1416
+ return `${urlStr}${sep}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
1417
+ }
1418
+ }
1419
+ normalizeRedirectUri(urlStr) {
1420
+ try {
1421
+ const u = new URL(urlStr);
1422
+ const path = u.pathname !== '/' ? u.pathname.replace(/\/+$/, '') : '/';
1423
+ const port = u.port ? `:${u.port}` : '';
1424
+ return `${u.protocol}//${u.hostname}${port}${path}${u.search}`;
1425
+ }
1426
+ catch {
1427
+ return String(urlStr || '').trim();
1428
+ }
1429
+ }
1430
+ redirectOAuthError(res, redirectUri, state, error, errorDescription) {
1431
+ let location = this.appendQueryParam(redirectUri, 'error', error);
1432
+ location = this.appendQueryParam(location, 'error_description', errorDescription);
1433
+ if (state)
1434
+ location = this.appendQueryParam(location, 'state', state);
1435
+ res.redirect(location);
1436
+ }
1437
+ async issueSession(res, username, workspaceId, role, profile) {
1438
+ const accessToken = (0, token_utils_1.createAccessToken)(username, this.deps.authCookieSecret, this.deps.authAccessTtlSec, workspaceId, role, profile?.displayName || username, profile?.email, profile?.avatarUrl, profile?.createdDate, profile?.lastSignInDate);
1439
+ const refreshToken = (0, token_utils_1.createRefreshToken)();
1440
+ const refreshTokenHash = (0, token_utils_1.hashRefreshToken)(refreshToken, this.deps.authCookieSecret);
1441
+ const refreshExpiresAt = new Date(Date.now() + this.deps.authRefreshTtlSec * 1000).toISOString();
1442
+ await this.deps.ensureDataStore().saveRefreshToken(refreshTokenHash, username, refreshExpiresAt);
1443
+ this.deps.setCookie(res, this.deps.accessCookieName, accessToken, this.deps.authAccessTtlSec);
1444
+ this.deps.setCookie(res, this.deps.refreshCookieName, refreshToken, this.deps.authRefreshTtlSec);
1445
+ }
1446
+ normalizeSupabaseUsername(email, userId) {
1447
+ const raw = String(email || '').trim().toLowerCase();
1448
+ if (raw) {
1449
+ const localPart = (raw.split('@')[0] || '').trim().toLowerCase();
1450
+ const normalizedLocal = localPart.replace(/[^a-z0-9._-]/g, '_');
1451
+ const shortHash = crypto_1.default.createHash('sha1').update(raw).digest('hex').slice(0, 8);
1452
+ const maxBaseLen = 64 - (shortHash.length + 1);
1453
+ const fallbackBase = raw.replace(/[^a-z0-9._-]/g, '_');
1454
+ const baseSource = normalizedLocal.length >= 3 ? normalizedLocal : fallbackBase;
1455
+ const base = baseSource.slice(0, Math.max(3, maxBaseLen));
1456
+ const candidate = `${base}_${shortHash}`;
1457
+ if (candidate.length >= 3)
1458
+ return candidate.slice(0, 64);
1459
+ }
1460
+ const fallback = String(userId || '').trim().toLowerCase().replace(/[^a-z0-9._-]/g, '_').slice(0, 64);
1461
+ return fallback || 'user';
1462
+ }
1463
+ resolveSupabaseRole(username, email) {
1464
+ const authProperty = this.deps.getAuthProperty();
1465
+ const normalizedEmail = String(email || '').trim().toLowerCase();
1466
+ const isAdminEmail = authProperty.adminEmails.some((v) => v.toLowerCase() === normalizedEmail);
1467
+ const isLegacyAdmin = this.deps.authAdminUsers.some((u) => u.username === username || u.username === normalizedEmail);
1468
+ return (isAdminEmail || isLegacyAdmin) ? 'admin' : 'user';
1469
+ }
1470
+ }
1471
+ exports.AuthApi = AuthApi;
1472
+ //# sourceMappingURL=authApi.js.map