@ipul-labs/forge 1.22.0 → 1.35.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 (294) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -78
  3. package/dist/apple-touch-icon.png +0 -0
  4. package/dist/assets/index-BmEuPeOG.js +27 -0
  5. package/dist/assets/index-CSH1OQYQ.css +1 -0
  6. package/dist/boot-check.js +98 -0
  7. package/dist/favicon.svg +8 -0
  8. package/dist/icon-192.png +0 -0
  9. package/dist/icon-512.png +0 -0
  10. package/dist/icon.svg +12 -0
  11. package/dist/index.html +33 -0
  12. package/dist/manifest.webmanifest +18 -0
  13. package/dist/theme-init.js +11 -0
  14. package/lib/bin/cli/analyze.d.ts.map +1 -1
  15. package/lib/bin/cli/analyze.js +3 -44
  16. package/lib/bin/cli/analyze.js.map +1 -1
  17. package/lib/bin/cli/apply.d.ts.map +1 -1
  18. package/lib/bin/cli/apply.js +2 -20
  19. package/lib/bin/cli/apply.js.map +1 -1
  20. package/lib/bin/cli/cifix.d.ts.map +1 -1
  21. package/lib/bin/cli/cifix.js +4 -28
  22. package/lib/bin/cli/cifix.js.map +1 -1
  23. package/lib/bin/cli/close.d.ts.map +1 -1
  24. package/lib/bin/cli/close.js +2 -33
  25. package/lib/bin/cli/close.js.map +1 -1
  26. package/lib/bin/cli/createIssue.d.ts.map +1 -1
  27. package/lib/bin/cli/createIssue.js +3 -12
  28. package/lib/bin/cli/createIssue.js.map +1 -1
  29. package/lib/bin/cli/doctor.d.ts.map +1 -1
  30. package/lib/bin/cli/doctor.js +3 -4
  31. package/lib/bin/cli/doctor.js.map +1 -1
  32. package/lib/bin/cli/init.js +1 -10
  33. package/lib/bin/cli/init.js.map +1 -1
  34. package/lib/bin/cli/issue.d.ts.map +1 -1
  35. package/lib/bin/cli/issue.js +3 -12
  36. package/lib/bin/cli/issue.js.map +1 -1
  37. package/lib/bin/cli/issues.d.ts.map +1 -1
  38. package/lib/bin/cli/issues.js +3 -12
  39. package/lib/bin/cli/issues.js.map +1 -1
  40. package/lib/bin/cli/merge.d.ts.map +1 -1
  41. package/lib/bin/cli/merge.js +4 -20
  42. package/lib/bin/cli/merge.js.map +1 -1
  43. package/lib/bin/cli/refine.d.ts.map +1 -1
  44. package/lib/bin/cli/refine.js +2 -26
  45. package/lib/bin/cli/refine.js.map +1 -1
  46. package/lib/bin/cli/repl.d.ts.map +1 -1
  47. package/lib/bin/cli/repl.js +4 -13
  48. package/lib/bin/cli/repl.js.map +1 -1
  49. package/lib/bin/cli/repos.d.ts.map +1 -1
  50. package/lib/bin/cli/repos.js +1 -11
  51. package/lib/bin/cli/repos.js.map +1 -1
  52. package/lib/bin/cli/resetPassword.d.ts +21 -0
  53. package/lib/bin/cli/resetPassword.d.ts.map +1 -0
  54. package/lib/bin/cli/resetPassword.js +69 -0
  55. package/lib/bin/cli/resetPassword.js.map +1 -0
  56. package/lib/bin/cli/resolution.d.ts.map +1 -1
  57. package/lib/bin/cli/resolution.js +2 -19
  58. package/lib/bin/cli/resolution.js.map +1 -1
  59. package/lib/bin/cli/serverClient.d.ts +2 -0
  60. package/lib/bin/cli/serverClient.d.ts.map +1 -1
  61. package/lib/bin/cli/serverClient.js +8 -4
  62. package/lib/bin/cli/serverClient.js.map +1 -1
  63. package/lib/bin/cli/session.d.ts.map +1 -1
  64. package/lib/bin/cli/session.js.map +1 -1
  65. package/lib/bin/cli/sessionStore.d.ts +1 -1
  66. package/lib/bin/cli/sessionStore.js +2 -2
  67. package/lib/bin/cli/sessionStore.js.map +1 -1
  68. package/lib/bin/cli/ship.d.ts.map +1 -1
  69. package/lib/bin/cli/ship.js +15 -23
  70. package/lib/bin/cli/ship.js.map +1 -1
  71. package/lib/bin/cli/update.d.ts.map +1 -1
  72. package/lib/bin/cli/update.js +34 -28
  73. package/lib/bin/cli/update.js.map +1 -1
  74. package/lib/bin/cli/updatePlan.d.ts +30 -0
  75. package/lib/bin/cli/updatePlan.d.ts.map +1 -0
  76. package/lib/bin/cli/updatePlan.js +50 -0
  77. package/lib/bin/cli/updatePlan.js.map +1 -0
  78. package/lib/bin/cli/utils.d.ts +3 -0
  79. package/lib/bin/cli/utils.d.ts.map +1 -1
  80. package/lib/bin/cli/utils.js +7 -23
  81. package/lib/bin/cli/utils.js.map +1 -1
  82. package/lib/bin/cli/validate.d.ts.map +1 -1
  83. package/lib/bin/cli/validate.js +3 -25
  84. package/lib/bin/cli/validate.js.map +1 -1
  85. package/lib/bin/cli/watch.d.ts.map +1 -1
  86. package/lib/bin/cli/watch.js +3 -12
  87. package/lib/bin/cli/watch.js.map +1 -1
  88. package/lib/bin/forge-agent.js +10 -22
  89. package/lib/bin/forge-agent.js.map +1 -1
  90. package/lib/server/app.d.ts +24 -0
  91. package/lib/server/app.d.ts.map +1 -0
  92. package/lib/server/app.js +58 -0
  93. package/lib/server/app.js.map +1 -0
  94. package/lib/server/forge/analyses/analysisStore.d.ts +53 -0
  95. package/lib/server/forge/analyses/analysisStore.d.ts.map +1 -0
  96. package/lib/server/forge/analyses/analysisStore.js +77 -0
  97. package/lib/server/forge/analyses/analysisStore.js.map +1 -0
  98. package/lib/server/forge/auth/apiTokens.d.ts +34 -0
  99. package/lib/server/forge/auth/apiTokens.d.ts.map +1 -0
  100. package/lib/server/forge/auth/apiTokens.js +44 -0
  101. package/lib/server/forge/auth/apiTokens.js.map +1 -0
  102. package/lib/server/forge/auth/auditLog.d.ts +36 -0
  103. package/lib/server/forge/auth/auditLog.d.ts.map +1 -0
  104. package/lib/server/forge/auth/auditLog.js +56 -0
  105. package/lib/server/forge/auth/auditLog.js.map +1 -0
  106. package/lib/server/forge/auth/authConfig.d.ts +13 -0
  107. package/lib/server/forge/auth/authConfig.d.ts.map +1 -0
  108. package/lib/server/forge/auth/authConfig.js +37 -0
  109. package/lib/server/forge/auth/authConfig.js.map +1 -0
  110. package/lib/server/forge/auth/createAuth.d.ts +130 -0
  111. package/lib/server/forge/auth/createAuth.d.ts.map +1 -0
  112. package/lib/server/forge/auth/createAuth.js +387 -0
  113. package/lib/server/forge/auth/createAuth.js.map +1 -0
  114. package/lib/server/forge/auth/middleware.d.ts +10 -0
  115. package/lib/server/forge/auth/middleware.d.ts.map +1 -0
  116. package/lib/server/forge/auth/middleware.js +22 -0
  117. package/lib/server/forge/auth/middleware.js.map +1 -0
  118. package/lib/server/forge/auth/oidc.d.ts +38 -0
  119. package/lib/server/forge/auth/oidc.d.ts.map +1 -0
  120. package/lib/server/forge/auth/oidc.js +63 -0
  121. package/lib/server/forge/auth/oidc.js.map +1 -0
  122. package/lib/server/forge/auth/passwords.d.ts +7 -0
  123. package/lib/server/forge/auth/passwords.d.ts.map +1 -0
  124. package/lib/server/forge/auth/passwords.js +28 -0
  125. package/lib/server/forge/auth/passwords.js.map +1 -0
  126. package/lib/server/forge/auth/resetPassword.d.ts +14 -0
  127. package/lib/server/forge/auth/resetPassword.d.ts.map +1 -0
  128. package/lib/server/forge/auth/resetPassword.js +29 -0
  129. package/lib/server/forge/auth/resetPassword.js.map +1 -0
  130. package/lib/server/forge/auth/roles.d.ts +7 -0
  131. package/lib/server/forge/auth/roles.d.ts.map +1 -0
  132. package/lib/server/forge/auth/roles.js +15 -0
  133. package/lib/server/forge/auth/roles.js.map +1 -0
  134. package/lib/server/forge/auth/sessions.d.ts +27 -0
  135. package/lib/server/forge/auth/sessions.d.ts.map +1 -0
  136. package/lib/server/forge/auth/sessions.js +37 -0
  137. package/lib/server/forge/auth/sessions.js.map +1 -0
  138. package/lib/server/forge/auth/testHarness.d.ts +104 -0
  139. package/lib/server/forge/auth/testHarness.d.ts.map +1 -0
  140. package/lib/server/forge/auth/testHarness.js +53 -0
  141. package/lib/server/forge/auth/testHarness.js.map +1 -0
  142. package/lib/server/forge/auth/tokenHash.d.ts +3 -0
  143. package/lib/server/forge/auth/tokenHash.d.ts.map +1 -0
  144. package/lib/server/forge/auth/tokenHash.js +4 -0
  145. package/lib/server/forge/auth/tokenHash.js.map +1 -0
  146. package/lib/server/forge/auth/users.d.ts +51 -0
  147. package/lib/server/forge/auth/users.d.ts.map +1 -0
  148. package/lib/server/forge/auth/users.js +118 -0
  149. package/lib/server/forge/auth/users.js.map +1 -0
  150. package/lib/server/forge/core/ForgeAgent.d.ts +43 -1
  151. package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -1
  152. package/lib/server/forge/core/ForgeAgent.js +209 -61
  153. package/lib/server/forge/core/ForgeAgent.js.map +1 -1
  154. package/lib/server/forge/core/agentClient.d.ts +7 -23
  155. package/lib/server/forge/core/agentClient.d.ts.map +1 -1
  156. package/lib/server/forge/core/agentClient.js +10 -25
  157. package/lib/server/forge/core/agentClient.js.map +1 -1
  158. package/lib/server/forge/core/claudeClient.d.ts +24 -1
  159. package/lib/server/forge/core/claudeClient.d.ts.map +1 -1
  160. package/lib/server/forge/core/claudeClient.js +25 -7
  161. package/lib/server/forge/core/claudeClient.js.map +1 -1
  162. package/lib/server/forge/core/configStore.d.ts +11 -1
  163. package/lib/server/forge/core/configStore.d.ts.map +1 -1
  164. package/lib/server/forge/core/configStore.js +33 -2
  165. package/lib/server/forge/core/configStore.js.map +1 -1
  166. package/lib/server/forge/core/dependencyPaths.d.ts +19 -0
  167. package/lib/server/forge/core/dependencyPaths.d.ts.map +1 -0
  168. package/lib/server/forge/core/dependencyPaths.js +32 -0
  169. package/lib/server/forge/core/dependencyPaths.js.map +1 -0
  170. package/lib/server/forge/core/evidence.d.ts +43 -0
  171. package/lib/server/forge/core/evidence.d.ts.map +1 -0
  172. package/lib/server/forge/core/evidence.js +108 -0
  173. package/lib/server/forge/core/evidence.js.map +1 -0
  174. package/lib/server/forge/core/jobs.d.ts +2 -0
  175. package/lib/server/forge/core/jobs.d.ts.map +1 -1
  176. package/lib/server/forge/core/jobs.js +8 -0
  177. package/lib/server/forge/core/jobs.js.map +1 -1
  178. package/lib/server/forge/core/localRepo.d.ts +31 -10
  179. package/lib/server/forge/core/localRepo.d.ts.map +1 -1
  180. package/lib/server/forge/core/localRepo.js +48 -82
  181. package/lib/server/forge/core/localRepo.js.map +1 -1
  182. package/lib/server/forge/core/prompts.d.ts +3 -1
  183. package/lib/server/forge/core/prompts.d.ts.map +1 -1
  184. package/lib/server/forge/core/prompts.js +14 -5
  185. package/lib/server/forge/core/prompts.js.map +1 -1
  186. package/lib/server/forge/core/providers/azuredevops.d.ts +4 -2
  187. package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -1
  188. package/lib/server/forge/core/providers/azuredevops.js +12 -2
  189. package/lib/server/forge/core/providers/azuredevops.js.map +1 -1
  190. package/lib/server/forge/core/providers/base.d.ts +29 -4
  191. package/lib/server/forge/core/providers/base.d.ts.map +1 -1
  192. package/lib/server/forge/core/providers/base.js +21 -3
  193. package/lib/server/forge/core/providers/base.js.map +1 -1
  194. package/lib/server/forge/core/providers/github.d.ts +7 -7
  195. package/lib/server/forge/core/providers/github.d.ts.map +1 -1
  196. package/lib/server/forge/core/providers/github.js +13 -4
  197. package/lib/server/forge/core/providers/github.js.map +1 -1
  198. package/lib/server/forge/core/providers/gitlab.d.ts +20 -3
  199. package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -1
  200. package/lib/server/forge/core/providers/gitlab.js +65 -5
  201. package/lib/server/forge/core/providers/gitlab.js.map +1 -1
  202. package/lib/server/forge/core/providers/types.d.ts +20 -0
  203. package/lib/server/forge/core/providers/types.d.ts.map +1 -1
  204. package/lib/server/forge/core/secretBox.d.ts +17 -0
  205. package/lib/server/forge/core/secretBox.d.ts.map +1 -0
  206. package/lib/server/forge/core/secretBox.js +74 -0
  207. package/lib/server/forge/core/secretBox.js.map +1 -0
  208. package/lib/server/forge/core/types.d.ts +7 -1
  209. package/lib/server/forge/core/types.d.ts.map +1 -1
  210. package/lib/server/forge/core/validator/clone.d.ts +5 -4
  211. package/lib/server/forge/core/validator/clone.d.ts.map +1 -1
  212. package/lib/server/forge/core/validator/clone.js +6 -86
  213. package/lib/server/forge/core/validator/clone.js.map +1 -1
  214. package/lib/server/forge/core/validator/cmd.d.ts.map +1 -1
  215. package/lib/server/forge/core/validator/cmd.js +4 -1
  216. package/lib/server/forge/core/validator/cmd.js.map +1 -1
  217. package/lib/server/forge/core/validator/projectType.d.ts.map +1 -1
  218. package/lib/server/forge/core/validator/projectType.js +2 -1
  219. package/lib/server/forge/core/validator/projectType.js.map +1 -1
  220. package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -1
  221. package/lib/server/forge/core/validator/runners/node.js +6 -2
  222. package/lib/server/forge/core/validator/runners/node.js.map +1 -1
  223. package/lib/server/forge/core/validator.d.ts.map +1 -1
  224. package/lib/server/forge/core/validator.js +28 -22
  225. package/lib/server/forge/core/validator.js.map +1 -1
  226. package/lib/server/forge/core/workspaces.d.ts +70 -0
  227. package/lib/server/forge/core/workspaces.d.ts.map +1 -0
  228. package/lib/server/forge/core/workspaces.js +348 -0
  229. package/lib/server/forge/core/workspaces.js.map +1 -0
  230. package/lib/server/forge/db/database.d.ts +22 -0
  231. package/lib/server/forge/db/database.d.ts.map +1 -0
  232. package/lib/server/forge/db/database.js +71 -0
  233. package/lib/server/forge/db/database.js.map +1 -0
  234. package/lib/server/forge/db/migrations.d.ts +11 -0
  235. package/lib/server/forge/db/migrations.d.ts.map +1 -0
  236. package/lib/server/forge/db/migrations.js +87 -0
  237. package/lib/server/forge/db/migrations.js.map +1 -0
  238. package/lib/server/forge/routes.d.ts.map +1 -1
  239. package/lib/server/forge/routes.js +413 -163
  240. package/lib/server/forge/routes.js.map +1 -1
  241. package/lib/server/forge/sse.d.ts +10 -0
  242. package/lib/server/forge/sse.d.ts.map +1 -0
  243. package/lib/server/forge/sse.js +22 -0
  244. package/lib/server/forge/sse.js.map +1 -0
  245. package/lib/server/spa.d.ts +12 -0
  246. package/lib/server/spa.d.ts.map +1 -0
  247. package/lib/server/spa.js +23 -0
  248. package/lib/server/spa.js.map +1 -0
  249. package/lib/server.js +9 -24
  250. package/lib/server.js.map +1 -1
  251. package/lib/shared/events.d.ts +3 -0
  252. package/lib/shared/events.d.ts.map +1 -1
  253. package/lib/shared/events.js +3 -0
  254. package/lib/shared/events.js.map +1 -1
  255. package/lib/shared/packageRoot.d.ts +11 -0
  256. package/lib/shared/packageRoot.d.ts.map +1 -0
  257. package/lib/shared/packageRoot.js +23 -0
  258. package/lib/shared/packageRoot.js.map +1 -0
  259. package/lib/shared/project.d.ts +3 -0
  260. package/lib/shared/project.d.ts.map +1 -0
  261. package/lib/shared/project.js +3 -0
  262. package/lib/shared/project.js.map +1 -0
  263. package/package.json +24 -9
  264. package/lib/bin/cli/improve.d.ts +0 -2
  265. package/lib/bin/cli/improve.d.ts.map +0 -1
  266. package/lib/bin/cli/improve.js +0 -189
  267. package/lib/bin/cli/improve.js.map +0 -1
  268. package/lib/bin/cli/stats.d.ts +0 -2
  269. package/lib/bin/cli/stats.d.ts.map +0 -1
  270. package/lib/bin/cli/stats.js +0 -78
  271. package/lib/bin/cli/stats.js.map +0 -1
  272. package/lib/server/forge/core/k8s.d.ts +0 -18
  273. package/lib/server/forge/core/k8s.d.ts.map +0 -1
  274. package/lib/server/forge/core/k8s.js +0 -49
  275. package/lib/server/forge/core/k8s.js.map +0 -1
  276. package/lib/server/forge/core/openclawClient.d.ts +0 -61
  277. package/lib/server/forge/core/openclawClient.d.ts.map +0 -1
  278. package/lib/server/forge/core/openclawClient.js +0 -111
  279. package/lib/server/forge/core/openclawClient.js.map +0 -1
  280. package/lib/server/forge/core/quotaClient.d.ts +0 -48
  281. package/lib/server/forge/core/quotaClient.d.ts.map +0 -1
  282. package/lib/server/forge/core/quotaClient.js +0 -88
  283. package/lib/server/forge/core/quotaClient.js.map +0 -1
  284. package/lib/server/forge/core/selfImprove.d.ts +0 -43
  285. package/lib/server/forge/core/selfImprove.d.ts.map +0 -1
  286. package/lib/server/forge/core/selfImprove.js +0 -372
  287. package/lib/server/forge/core/selfImprove.js.map +0 -1
  288. package/lib/server/forge/core/telemetry.d.ts +0 -100
  289. package/lib/server/forge/core/telemetry.d.ts.map +0 -1
  290. package/lib/server/forge/core/telemetry.js +0 -273
  291. package/lib/server/forge/core/telemetry.js.map +0 -1
  292. package/scripts/fix-shebang.cjs +0 -11
  293. package/scripts/inject-readme.cjs +0 -23
  294. package/scripts/local-rollout.sh +0 -134
@@ -0,0 +1,14 @@
1
+ import type { Db } from "../db/database.ts";
2
+ import { type User } from "./users.ts";
3
+ /**
4
+ * Recovery path for a locked-out local account, run by `forge reset-password`
5
+ * on the host. Whoever can open the database file already controls the
6
+ * install, so this needs no sign-in. Signs the account out everywhere, since a
7
+ * reset usually means the old password can't be trusted.
8
+ */
9
+ export declare function resetPassword({ db, email, password }: {
10
+ db: Db;
11
+ email: string;
12
+ password: string;
13
+ }): Promise<User>;
14
+ //# sourceMappingURL=resetPassword.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resetPassword.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/resetPassword.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAI5C,OAAO,EAAe,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBvH"}
@@ -0,0 +1,29 @@
1
+ import { createAuditLog } from "./auditLog.js";
2
+ import { createSessions } from "./sessions.js";
3
+ import { createUsers } from "./users.js";
4
+ /**
5
+ * Recovery path for a locked-out local account, run by `forge reset-password`
6
+ * on the host. Whoever can open the database file already controls the
7
+ * install, so this needs no sign-in. Signs the account out everywhere, since a
8
+ * reset usually means the old password can't be trusted.
9
+ */
10
+ export async function resetPassword({ db, email, password }) {
11
+ const users = createUsers({ db });
12
+ const user = await users.findByEmail(email);
13
+ if (!user)
14
+ throw new Error(`No Forge account for ${email}.`);
15
+ if (user.authProvider !== "local") {
16
+ throw new Error(`${user.email} signs in with SSO, so its password is managed by your identity provider.`);
17
+ }
18
+ await users.update(user.id, { password });
19
+ await createSessions({ db }).revokeAllForUser(user.id);
20
+ await createAuditLog({ db }).record({
21
+ actorId: null,
22
+ actorLabel: "cli",
23
+ action: "user.password_reset",
24
+ outcome: "success",
25
+ target: user.email,
26
+ });
27
+ return user;
28
+ }
29
+ //# sourceMappingURL=resetPassword.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resetPassword.js","sourceRoot":"","sources":["../../../../server/forge/auth/resetPassword.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAA+C;IACvG,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,GAAG,CAAC,CAAC;IAC7D,IAAI,IAAI,CAAC,YAAY,KAAK,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,2EAA2E,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1C,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvD,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;QACnC,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,KAAK;QACjB,MAAM,EAAE,qBAAqB;QAC7B,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** Ordered least → most privileged. Each role includes everything below it. */
2
+ export declare const ROLES: readonly ["viewer", "operator", "approver", "admin"];
3
+ export type Role = (typeof ROLES)[number];
4
+ export declare const ROLE_DESCRIPTIONS: Record<Role, string>;
5
+ export declare function isRole(value: unknown): value is Role;
6
+ export declare function hasRole(actual: Role, required: Role): boolean;
7
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/roles.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,KAAK,sDAAuD,CAAC;AAC1E,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1C,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAKlD,CAAC;AAEF,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAEpD;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,OAAO,CAE7D"}
@@ -0,0 +1,15 @@
1
+ /** Ordered least → most privileged. Each role includes everything below it. */
2
+ export const ROLES = ["viewer", "operator", "approver", "admin"];
3
+ export const ROLE_DESCRIPTIONS = {
4
+ viewer: "Read issues, merge requests, pipelines and runs.",
5
+ operator: "Analyze issues, refine fixes, validate and open merge requests.",
6
+ approver: "Everything an operator can do, plus merge and close merge requests.",
7
+ admin: "Full access, including connections, repositories, members and the audit log.",
8
+ };
9
+ export function isRole(value) {
10
+ return typeof value === "string" && ROLES.includes(value);
11
+ }
12
+ export function hasRole(actual, required) {
13
+ return ROLES.indexOf(actual) >= ROLES.indexOf(required);
14
+ }
15
+ //# sourceMappingURL=roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../../../server/forge/auth/roles.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,CAAU,CAAC;AAG1E,MAAM,CAAC,MAAM,iBAAiB,GAAyB;IACtD,MAAM,EAAE,kDAAkD;IAC1D,QAAQ,EAAE,iEAAiE;IAC3E,QAAQ,EAAE,qEAAqE;IAC/E,KAAK,EAAE,8EAA8E;CACrF,CAAC;AAEF,MAAM,UAAU,MAAM,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,KAA2B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAY,EAAE,QAAc;IACnD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { Db } from "../db/database.ts";
2
+ export declare const SESSION_IDLE_MS: number;
3
+ export declare const SESSION_ABSOLUTE_MS: number;
4
+ /** Opaque session tokens; only their SHA-256 is stored. */
5
+ export declare function createSessions({ db, clock, idleMs, absoluteMs, }: {
6
+ db: Db;
7
+ clock?: () => number;
8
+ idleMs?: number;
9
+ absoluteMs?: number;
10
+ }): {
11
+ absoluteMs: number;
12
+ create({ userId, ip, userAgent }: {
13
+ userId: string;
14
+ ip: string;
15
+ userAgent: string;
16
+ }): Promise<{
17
+ token: string;
18
+ }>;
19
+ resolve(token: string): Promise<{
20
+ userId: string;
21
+ createdAt: number;
22
+ }>;
23
+ revoke(token: string): Promise<void>;
24
+ revokeAllForUser(userId: string): Promise<void>;
25
+ };
26
+ export type Sessions = ReturnType<typeof createSessions>;
27
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAI5C,eAAO,MAAM,eAAe,QAAmB,CAAC;AAChD,eAAO,MAAM,mBAAmB,QAAuB,CAAC;AAUxD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,EAC9B,EAAE,EACF,KAAgB,EAChB,MAAwB,EACxB,UAAgC,GAChC,EAAE;IACF,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;;sCAIyC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;;;mBAUpE,MAAM;;;;kBAcP,MAAM;6BAIK,MAAM;EAItC;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { newSecret, sha256 } from "./tokenHash.js";
2
+ export const SESSION_IDLE_MS = 12 * 60 * 60_000;
3
+ export const SESSION_ABSOLUTE_MS = 7 * 24 * 60 * 60_000;
4
+ /** Opaque session tokens; only their SHA-256 is stored. */
5
+ export function createSessions({ db, clock = Date.now, idleMs = SESSION_IDLE_MS, absoluteMs = SESSION_ABSOLUTE_MS, }) {
6
+ return {
7
+ absoluteMs,
8
+ async create({ userId, ip, userAgent }) {
9
+ const token = newSecret();
10
+ const now = clock();
11
+ await db.run("INSERT INTO sessions (id_hash, user_id, created_at, last_seen_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?)", [sha256(token), userId, now, now, now + absoluteMs, ip, userAgent.slice(0, 300)]);
12
+ return { token };
13
+ },
14
+ async resolve(token) {
15
+ if (!token)
16
+ return null;
17
+ const hash = sha256(token);
18
+ const row = await db.get("SELECT * FROM sessions WHERE id_hash = ?", [hash]);
19
+ if (!row)
20
+ return null;
21
+ const now = clock();
22
+ if (now >= row.expires_at || now - row.last_seen_at > idleMs) {
23
+ await db.run("DELETE FROM sessions WHERE id_hash = ?", [hash]);
24
+ return null;
25
+ }
26
+ await db.run("UPDATE sessions SET last_seen_at = ? WHERE id_hash = ?", [now, hash]);
27
+ return { userId: row.user_id, createdAt: row.created_at };
28
+ },
29
+ async revoke(token) {
30
+ await db.run("DELETE FROM sessions WHERE id_hash = ?", [sha256(token)]);
31
+ },
32
+ async revokeAllForUser(userId) {
33
+ await db.run("DELETE FROM sessions WHERE user_id = ?", [userId]);
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../../server/forge/auth/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;AAUxD,2DAA2D;AAC3D,MAAM,UAAU,cAAc,CAAC,EAC9B,EAAE,EACF,KAAK,GAAG,IAAI,CAAC,GAAG,EAChB,MAAM,GAAG,eAAe,EACxB,UAAU,GAAG,mBAAmB,GAMhC;IACA,OAAO;QACN,UAAU;QAEV,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAqD;YACxF,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC;YACpB,MAAM,EAAE,CAAC,GAAG,CACX,4HAA4H,EAC5H,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,UAAU,EAAE,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAChF,CAAC;YACF,OAAO,EAAE,KAAK,EAAE,CAAC;QAClB,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,KAAa;YAC1B,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACxB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,GAAG,CAAa,0CAA0C,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YACzF,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YACtB,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC;YACpB,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,GAAG,GAAG,CAAC,YAAY,GAAG,MAAM,EAAE,CAAC;gBAC9D,MAAM,EAAE,CAAC,GAAG,CAAC,wCAAwC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC/D,OAAO,IAAI,CAAC;YACb,CAAC;YACD,MAAM,EAAE,CAAC,GAAG,CAAC,wDAAwD,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACpF,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3D,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,KAAa;YACzB,MAAM,EAAE,CAAC,GAAG,CAAC,wCAAwC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzE,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,MAAc;YACpC,MAAM,EAAE,CAAC,GAAG,CAAC,wCAAwC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QAClE,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,104 @@
1
+ import express, { type Router } from "express";
2
+ import { type AuthOptions } from "./createAuth.ts";
3
+ export declare const SERVICE_TOKEN = "service-token-for-tests-0123456789";
4
+ /** Boots an express app with the auth stack and a few probe routes. */
5
+ export declare function startAuthApp(opts?: Partial<AuthOptions> & {
6
+ extra?: (r: Router) => void;
7
+ }): Promise<{
8
+ auth: {
9
+ users: {
10
+ count(): Promise<number>;
11
+ countActiveAdmins(): Promise<number>;
12
+ findById(id: string): Promise<import("./users.ts").User>;
13
+ findByEmail(email: string): Promise<import("./users.ts").User>;
14
+ list(): Promise<import("./users.ts").User[]>;
15
+ create({ email, name, role, password }: {
16
+ email: string;
17
+ name: string;
18
+ role: import("./roles.ts").Role;
19
+ password?: string;
20
+ }): Promise<import("./users.ts").User>;
21
+ update(id: string, patch: {
22
+ role?: import("./roles.ts").Role;
23
+ disabled?: boolean;
24
+ name?: string;
25
+ password?: string;
26
+ }): Promise<import("./users.ts").User>;
27
+ authenticate(email: string, password: string): Promise<import("./users.ts").AuthResult>;
28
+ upsertOidc({ subject, email, name, defaultRole }: {
29
+ subject: string;
30
+ email: string;
31
+ name: string;
32
+ defaultRole: import("./roles.ts").Role;
33
+ }): Promise<import("./users.ts").User>;
34
+ };
35
+ sessions: {
36
+ absoluteMs: number;
37
+ create({ userId, ip, userAgent }: {
38
+ userId: string;
39
+ ip: string;
40
+ userAgent: string;
41
+ }): Promise<{
42
+ token: string;
43
+ }>;
44
+ resolve(token: string): Promise<{
45
+ userId: string;
46
+ createdAt: number;
47
+ }>;
48
+ revoke(token: string): Promise<void>;
49
+ revokeAllForUser(userId: string): Promise<void>;
50
+ };
51
+ tokens: {
52
+ create({ userId, name }: {
53
+ userId: string;
54
+ name: string;
55
+ }): Promise<{
56
+ token: string;
57
+ record: import("./apiTokens.ts").ApiTokenRecord;
58
+ }>;
59
+ list(userId: string): Promise<import("./apiTokens.ts").ApiTokenRecord[]>;
60
+ resolve(token: string): Promise<{
61
+ userId: string;
62
+ tokenId: string;
63
+ }>;
64
+ revoke({ id, userId }: {
65
+ id: string;
66
+ userId: string;
67
+ }): Promise<boolean>;
68
+ revokeAllForUser(userId: string): Promise<void>;
69
+ };
70
+ audit: {
71
+ record(e: import("./auditLog.ts").AuditEventInput): Promise<void>;
72
+ list({ action, actorId, limit, before }: {
73
+ action?: string;
74
+ actorId?: string;
75
+ limit?: number;
76
+ before?: number;
77
+ }): Promise<{
78
+ events: import("./auditLog.ts").AuditEvent[];
79
+ nextCursor: number;
80
+ }>;
81
+ };
82
+ router: import("express-serve-static-core").Router;
83
+ adminRouter: import("express-serve-static-core").Router;
84
+ authenticate: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void>;
85
+ requireUser: (req: express.Request, res: express.Response, next: express.NextFunction) => void;
86
+ requireRole: typeof import("./middleware.ts").requireRole;
87
+ csrf: (req: express.Request, res: express.Response, next: express.NextFunction) => void;
88
+ auditTrail: (req: express.Request, res: express.Response, next: express.NextFunction) => void;
89
+ announceSetup: () => Promise<void>;
90
+ setOidcRedirectUri: (uri: string) => void;
91
+ };
92
+ db: import("../db/database.ts").Db;
93
+ base: string;
94
+ close: () => Promise<unknown>;
95
+ }>;
96
+ /** Minimal cookie-jar client. */
97
+ export declare function client(base: string): {
98
+ call: (path: string, init?: RequestInit & {
99
+ json?: unknown;
100
+ csrf?: boolean;
101
+ }) => Promise<Response>;
102
+ cookie: string;
103
+ };
104
+ //# sourceMappingURL=testHarness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testHarness.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/testHarness.ts"],"names":[],"mappings":"AAEA,OAAO,OAAO,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE/D,eAAO,MAAM,aAAa,uCAAuC,CAAC;AAElE,uEAAuE;AACvE,wBAAsB,YAAY,CAAC,IAAI,GAAE,OAAO,CAAC,WAAW,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO;;;;;;;;;;;;wBAuC1E,CAAC;;;oBAM+jB,CAAC;wBAAgB,CAAC;oBAAe,CAAC;wBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA1B7nB,CAAC;uBAAiB,CAAC;qBAAe,CAAC;sBAAgB,CAAC;;;;;;;;;;;;;;;;;;;GACrE;AAED,iCAAiC;AACjC,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM;iBAER,MAAM,SAAQ,WAAW,GAAG;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE;YAkB1B,MAAM;EACpE"}
@@ -0,0 +1,53 @@
1
+ import express from "express";
2
+ import { openDatabase } from "../db/database.js";
3
+ import { createAuth } from "./createAuth.js";
4
+ export const SERVICE_TOKEN = "service-token-for-tests-0123456789";
5
+ /** Boots an express app with the auth stack and a few probe routes. */
6
+ export async function startAuthApp(opts = {}) {
7
+ const db = await openDatabase({ path: ":memory:" });
8
+ const auth = createAuth({ db, serviceToken: SERVICE_TOKEN, cookieSecure: false, ...opts });
9
+ const app = express();
10
+ app.use(express.json());
11
+ const api = express.Router();
12
+ api.use("/auth", auth.router);
13
+ api.use(auth.authenticate, auth.requireUser, auth.csrf, auth.auditTrail);
14
+ api.use("/admin", auth.adminRouter);
15
+ api.get("/probe", (req, res) => res.json({ principal: req.principal }));
16
+ api.post("/operate", auth.requireRole("operator"), (_req, res) => res.json({ ok: true }));
17
+ api.post("/mr/:mrIid/merge", auth.requireRole("approver"), (_req, res) => res.json({ merged: true }));
18
+ opts.extra?.(api);
19
+ app.use("/api/forge", api);
20
+ // Bind where the tests connect: on macOS a bare listen(0) takes the IPv6
21
+ // wildcard, and another process can hold the same port on 127.0.0.1.
22
+ const server = app.listen(0, "127.0.0.1");
23
+ await new Promise((r) => server.once("listening", r));
24
+ const base = `http://127.0.0.1:${server.address().port}/api/forge`;
25
+ return { auth, db, base, close: () => new Promise((r) => server.close(r)) };
26
+ }
27
+ /** Minimal cookie-jar client. */
28
+ export function client(base) {
29
+ let cookie = "";
30
+ const call = async (path, init = {}) => {
31
+ const headers = { ...init.headers };
32
+ if (cookie)
33
+ headers.cookie = cookie;
34
+ if (init.json !== undefined)
35
+ headers["content-type"] = "application/json";
36
+ if (init.csrf !== false && init.method && init.method !== "GET")
37
+ headers["x-forge-request"] = "1";
38
+ const res = await fetch(base + path, {
39
+ ...init,
40
+ headers,
41
+ body: init.json !== undefined ? JSON.stringify(init.json) : init.body,
42
+ redirect: "manual",
43
+ });
44
+ const set = res.headers.getSetCookie();
45
+ for (const c of set) {
46
+ const pair = c.split(";")[0];
47
+ cookie = /=$/.test(pair) || /Max-Age=0/i.test(c) ? "" : pair;
48
+ }
49
+ return res;
50
+ };
51
+ return { call, get cookie() { return cookie; }, set cookie(v) { cookie = v; } };
52
+ }
53
+ //# sourceMappingURL=testHarness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testHarness.js","sourceRoot":"","sources":["../../../../server/forge/auth/testHarness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const newSecret: () => string;
2
+ export declare const sha256: (value: string) => string;
3
+ //# sourceMappingURL=tokenHash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenHash.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/tokenHash.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,cAA8C,CAAC;AACrE,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,WAAqD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ export const newSecret = () => randomBytes(32).toString("base64url");
3
+ export const sha256 = (value) => createHash("sha256").update(value).digest("hex");
4
+ //# sourceMappingURL=tokenHash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenHash.js","sourceRoot":"","sources":["../../../../server/forge/auth/tokenHash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { Db } from "../db/database.ts";
2
+ import type { Role } from "./roles.ts";
3
+ export interface User {
4
+ id: string;
5
+ email: string;
6
+ name: string;
7
+ role: Role;
8
+ authProvider: "local" | "oidc";
9
+ disabled: boolean;
10
+ createdAt: number;
11
+ lastLoginAt: number | null;
12
+ }
13
+ export type AuthResult = {
14
+ ok: true;
15
+ user: User;
16
+ } | {
17
+ ok: false;
18
+ reason: "invalid" | "locked" | "disabled";
19
+ };
20
+ export declare function createUsers({ db, clock }: {
21
+ db: Db;
22
+ clock?: () => number;
23
+ }): {
24
+ count(): Promise<number>;
25
+ countActiveAdmins(): Promise<number>;
26
+ findById(id: string): Promise<User>;
27
+ findByEmail(email: string): Promise<User>;
28
+ list(): Promise<User[]>;
29
+ create({ email, name, role, password }: {
30
+ email: string;
31
+ name: string;
32
+ role: Role;
33
+ password?: string;
34
+ }): Promise<User>;
35
+ update(id: string, patch: {
36
+ role?: Role;
37
+ disabled?: boolean;
38
+ name?: string;
39
+ password?: string;
40
+ }): Promise<User>;
41
+ authenticate(email: string, password: string): Promise<AuthResult>;
42
+ /** Find by OIDC subject, else link by email, else provision. */
43
+ upsertOidc({ subject, email, name, defaultRole }: {
44
+ subject: string;
45
+ email: string;
46
+ name: string;
47
+ defaultRole: Role;
48
+ }): Promise<User>;
49
+ };
50
+ export type Users = ReturnType<typeof createUsers>;
51
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../../server/forge/auth/users.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAG5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAiBD,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAA;CAAE,CAAC;AAqB7G,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,KAAgB,EAAE,EAAE;IAAE,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CAAE;;;iBAajE,MAAM;uBAKA,MAAM;;4CASe;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;eAgB3F,MAAM,SAAS;QAAE,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;wBAe3E,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAuBxE,gEAAgE;sDACR;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,CAAA;KAAE;EAsB5H;AAED,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { hashPassword, validatePassword, verifyPassword } from "./passwords.js";
3
+ const MAX_FAILED_LOGINS = 5;
4
+ const LOCKOUT_MS = 15 * 60_000;
5
+ // Compared against when the email is unknown so response time doesn't reveal
6
+ // which addresses have accounts.
7
+ const DUMMY_HASH = hashPassword("forge-dummy-password-for-timing");
8
+ const toUser = (r) => ({
9
+ id: r.id,
10
+ email: r.email,
11
+ name: r.name,
12
+ role: r.role,
13
+ authProvider: r.auth_provider,
14
+ disabled: Boolean(r.disabled),
15
+ createdAt: r.created_at,
16
+ lastLoginAt: r.last_login_at,
17
+ });
18
+ const normalizeEmail = (email) => email.trim().toLowerCase();
19
+ export function createUsers({ db, clock = Date.now }) {
20
+ const rowByEmail = (email) => db.get("SELECT * FROM users WHERE email = ?", [normalizeEmail(email)]);
21
+ const rowById = (id) => db.get("SELECT * FROM users WHERE id = ?", [id]);
22
+ const users = {
23
+ async count() {
24
+ return (await db.get("SELECT COUNT(*) AS n FROM users")).n;
25
+ },
26
+ async countActiveAdmins() {
27
+ return (await db.get("SELECT COUNT(*) AS n FROM users WHERE role = 'admin' AND disabled = 0")).n;
28
+ },
29
+ async findById(id) {
30
+ const r = await rowById(id);
31
+ return r ? toUser(r) : null;
32
+ },
33
+ async findByEmail(email) {
34
+ const r = await rowByEmail(email);
35
+ return r ? toUser(r) : null;
36
+ },
37
+ async list() {
38
+ return (await db.all("SELECT * FROM users ORDER BY created_at, email")).map(toUser);
39
+ },
40
+ async create({ email, name, role, password }) {
41
+ const normalized = normalizeEmail(email);
42
+ if (!/^[^@\s]+@[^@\s]+$/.test(normalized))
43
+ throw new Error("Enter a valid email address.");
44
+ if (password !== undefined) {
45
+ const problem = validatePassword(password);
46
+ if (problem)
47
+ throw new Error(problem);
48
+ }
49
+ if (await rowByEmail(normalized))
50
+ throw new Error("A user with that email already exists.");
51
+ const id = randomUUID();
52
+ await db.run("INSERT INTO users (id, email, name, role, password_hash, auth_provider, created_at) VALUES (?, ?, ?, ?, ?, 'local', ?)", [id, normalized, name.trim() || normalized, role, password ? await hashPassword(password) : null, clock()]);
53
+ return toUser((await rowById(id)));
54
+ },
55
+ async update(id, patch) {
56
+ if (patch.password !== undefined) {
57
+ const problem = validatePassword(patch.password);
58
+ if (problem)
59
+ throw new Error(problem);
60
+ await db.run("UPDATE users SET password_hash = ?, failed_logins = 0, locked_until = NULL WHERE id = ?", [
61
+ await hashPassword(patch.password),
62
+ id,
63
+ ]);
64
+ }
65
+ if (patch.role !== undefined)
66
+ await db.run("UPDATE users SET role = ? WHERE id = ?", [patch.role, id]);
67
+ if (patch.disabled !== undefined)
68
+ await db.run("UPDATE users SET disabled = ? WHERE id = ?", [patch.disabled ? 1 : 0, id]);
69
+ if (patch.name !== undefined)
70
+ await db.run("UPDATE users SET name = ? WHERE id = ?", [patch.name.trim(), id]);
71
+ return users.findById(id);
72
+ },
73
+ async authenticate(email, password) {
74
+ const row = await rowByEmail(email ?? "");
75
+ if (!row || !row.password_hash) {
76
+ await verifyPassword(password ?? "", await DUMMY_HASH);
77
+ return { ok: false, reason: "invalid" };
78
+ }
79
+ const now = clock();
80
+ if (row.locked_until && row.locked_until > now)
81
+ return { ok: false, reason: "locked" };
82
+ if (!(await verifyPassword(password ?? "", row.password_hash))) {
83
+ const failures = row.failed_logins + 1;
84
+ const lock = failures >= MAX_FAILED_LOGINS;
85
+ await db.run("UPDATE users SET failed_logins = ?, locked_until = ? WHERE id = ?", [
86
+ lock ? 0 : failures,
87
+ lock ? now + LOCKOUT_MS : null,
88
+ row.id,
89
+ ]);
90
+ return { ok: false, reason: "invalid" };
91
+ }
92
+ if (row.disabled)
93
+ return { ok: false, reason: "disabled" };
94
+ await db.run("UPDATE users SET failed_logins = 0, locked_until = NULL, last_login_at = ? WHERE id = ?", [now, row.id]);
95
+ return { ok: true, user: toUser((await rowById(row.id))) };
96
+ },
97
+ /** Find by OIDC subject, else link by email, else provision. */
98
+ async upsertOidc({ subject, email, name, defaultRole }) {
99
+ const now = clock();
100
+ const bySubject = await db.get("SELECT * FROM users WHERE oidc_subject = ?", [subject]);
101
+ const existing = bySubject ?? (await rowByEmail(email));
102
+ if (existing) {
103
+ await db.run("UPDATE users SET oidc_subject = ?, name = ?, last_login_at = ? WHERE id = ?", [
104
+ subject,
105
+ name || existing.name,
106
+ now,
107
+ existing.id,
108
+ ]);
109
+ return toUser((await rowById(existing.id)));
110
+ }
111
+ const id = randomUUID();
112
+ await db.run("INSERT INTO users (id, email, name, role, auth_provider, oidc_subject, created_at, last_login_at) VALUES (?, ?, ?, ?, 'oidc', ?, ?, ?)", [id, normalizeEmail(email), name || email, defaultRole, subject, now, now]);
113
+ return toUser((await rowById(id)));
114
+ },
115
+ };
116
+ return users;
117
+ }
118
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../../../server/forge/auth/users.ts"],"names":[],"mappings":"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"}
@@ -88,6 +88,23 @@ interface SignalOpts {
88
88
  /** Per-request agent-mode override ("structured" | "agentic"); see agentMode.ts. */
89
89
  agentMode?: string;
90
90
  }
91
+ /** Inputs for one agentic run in a prepared checkout. */
92
+ interface AgenticRunOpts {
93
+ signal?: AbortSignal;
94
+ ciContext?: string;
95
+ userPrompt?: string;
96
+ /** Reviewer follow-up: the run is a refine of the change already in the tree. */
97
+ feedback?: string;
98
+ /** Changes already in the tree before the agent starts (refine overlays them). */
99
+ baseline?: ForgeChange[];
100
+ }
101
+ /** What an agentic run produced, before it is emitted. */
102
+ interface AgenticOutcome {
103
+ result: EnrichedAnalysisResult;
104
+ usage: ForgeEvent | null;
105
+ /** False when the agent left the tree exactly as it found it. */
106
+ edited: boolean;
107
+ }
91
108
  /** The result of the analysis "kind" argument. */
92
109
  type AnalysisKind = "fresh" | "new" | "update" | "retry";
93
110
  /** Result from applying changes to a single repo. */
@@ -116,7 +133,7 @@ export declare class ForgeAgent {
116
133
  provider: ScmProvider;
117
134
  constructor(opts?: ForgeAgentOptions);
118
135
  _provider(repo: string): ScmProvider;
119
- get _agentLabel(): "OpenClaw" | "Claude";
136
+ get _agentLabel(): string;
120
137
  get _isMultiRepo(): boolean;
121
138
  _fetchAllTrees(emit: (evt: ForgeEvent) => void): Promise<RepoTree[]>;
122
139
  _fetchFiles(entries: FileEntry[], emit: (evt: ForgeEvent) => void): Promise<FileContent[]>;
@@ -147,6 +164,30 @@ export declare class ForgeAgent {
147
164
  * pipeline so the route, UI, and apply flow are unchanged.
148
165
  */
149
166
  _analyzeAgentic(issueIid: number, emit: (evt: ForgeEvent) => void, model: string | undefined, ciContext: string | undefined, userPrompt: string | undefined, opts?: SignalOpts): Promise<EnrichedAnalysisResult>;
167
+ _runAgentic(dir: string, issue: NormalIssue, repo: string, emit: (evt: ForgeEvent) => void, model: string | undefined, ciContext: string | undefined, userPrompt: string | undefined, { signal }: SignalOpts): Promise<EnrichedAnalysisResult>;
168
+ /**
169
+ * Runs the agent in `dir` and reads back what it did: the captured changes,
170
+ * its summary and any evidence. Callers decide what to emit.
171
+ */
172
+ _executeAgentic(dir: string, issue: NormalIssue, repo: string, emit: (evt: ForgeEvent) => void, model: string | undefined, { signal, ciContext, userPrompt, feedback, baseline }: AgenticRunOpts): Promise<AgenticOutcome>;
173
+ /**
174
+ * Agentic refine: overlays the current proposal on the issue's worktree and
175
+ * runs the reviewer's follow-up as a full agent, so asks like "check it in
176
+ * Playwright" can run things instead of hitting the read-only refine's
177
+ * Read/Glob/Grep wall. A run that leaves the tree untouched and gathers no
178
+ * evidence comes back as an answer, so a question doesn't replace the
179
+ * proposal (and its validation) with an identical one.
180
+ */
181
+ _refineAgentic(issue: NormalIssue, previousChanges: ForgeChange[] | null, feedback: string, emit: (evt: ForgeEvent) => void, model: string | undefined, { signal }: SignalOpts): Promise<EnrichedAnalysisResult | {
182
+ kind: "answer";
183
+ answer: string;
184
+ }>;
185
+ /**
186
+ * Uploads whatever the agent left in the evidence folder and returns the
187
+ * markdown for the MR. A failed upload is logged, never fatal — the change
188
+ * itself is what matters.
189
+ */
190
+ _attachEvidence(dir: string, emit: (evt: ForgeEvent) => void): Promise<string | null>;
150
191
  /**
151
192
  * Runs the full analysis pipeline for an issue across all configured repos.
152
193
  *
@@ -281,6 +322,7 @@ export declare class ForgeAgent {
281
322
  isPrimary?: boolean;
282
323
  repo?: string;
283
324
  priorApplications?: ApplicationResult[];
325
+ evidence?: string;
284
326
  }): string;
285
327
  }
286
328
  export {};