@ipul-labs/forge 1.22.0 → 1.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +153 -78
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/index-CNvSOdpC.js +27 -0
- package/dist/assets/index-CSH1OQYQ.css +1 -0
- package/dist/boot-check.js +98 -0
- package/dist/favicon.svg +8 -0
- package/dist/icon-192.png +0 -0
- package/dist/icon-512.png +0 -0
- package/dist/icon.svg +12 -0
- package/dist/index.html +33 -0
- package/dist/manifest.webmanifest +18 -0
- package/dist/theme-init.js +11 -0
- package/lib/bin/cli/analyze.d.ts.map +1 -1
- package/lib/bin/cli/analyze.js +3 -44
- package/lib/bin/cli/analyze.js.map +1 -1
- package/lib/bin/cli/apply.d.ts.map +1 -1
- package/lib/bin/cli/apply.js +2 -20
- package/lib/bin/cli/apply.js.map +1 -1
- package/lib/bin/cli/cifix.d.ts.map +1 -1
- package/lib/bin/cli/cifix.js +4 -28
- package/lib/bin/cli/cifix.js.map +1 -1
- package/lib/bin/cli/close.d.ts.map +1 -1
- package/lib/bin/cli/close.js +2 -33
- package/lib/bin/cli/close.js.map +1 -1
- package/lib/bin/cli/createIssue.d.ts.map +1 -1
- package/lib/bin/cli/createIssue.js +3 -12
- package/lib/bin/cli/createIssue.js.map +1 -1
- package/lib/bin/cli/doctor.d.ts.map +1 -1
- package/lib/bin/cli/doctor.js +3 -4
- package/lib/bin/cli/doctor.js.map +1 -1
- package/lib/bin/cli/init.js +1 -10
- package/lib/bin/cli/init.js.map +1 -1
- package/lib/bin/cli/issue.d.ts.map +1 -1
- package/lib/bin/cli/issue.js +3 -12
- package/lib/bin/cli/issue.js.map +1 -1
- package/lib/bin/cli/issues.d.ts.map +1 -1
- package/lib/bin/cli/issues.js +3 -12
- package/lib/bin/cli/issues.js.map +1 -1
- package/lib/bin/cli/merge.d.ts.map +1 -1
- package/lib/bin/cli/merge.js +4 -20
- package/lib/bin/cli/merge.js.map +1 -1
- package/lib/bin/cli/refine.d.ts.map +1 -1
- package/lib/bin/cli/refine.js +2 -26
- package/lib/bin/cli/refine.js.map +1 -1
- package/lib/bin/cli/repl.d.ts.map +1 -1
- package/lib/bin/cli/repl.js +4 -13
- package/lib/bin/cli/repl.js.map +1 -1
- package/lib/bin/cli/repos.d.ts.map +1 -1
- package/lib/bin/cli/repos.js +1 -11
- package/lib/bin/cli/repos.js.map +1 -1
- package/lib/bin/cli/resetPassword.d.ts +21 -0
- package/lib/bin/cli/resetPassword.d.ts.map +1 -0
- package/lib/bin/cli/resetPassword.js +69 -0
- package/lib/bin/cli/resetPassword.js.map +1 -0
- package/lib/bin/cli/resolution.d.ts.map +1 -1
- package/lib/bin/cli/resolution.js +2 -19
- package/lib/bin/cli/resolution.js.map +1 -1
- package/lib/bin/cli/serverClient.d.ts +2 -0
- package/lib/bin/cli/serverClient.d.ts.map +1 -1
- package/lib/bin/cli/serverClient.js +8 -4
- package/lib/bin/cli/serverClient.js.map +1 -1
- package/lib/bin/cli/session.d.ts.map +1 -1
- package/lib/bin/cli/session.js.map +1 -1
- package/lib/bin/cli/sessionStore.d.ts +1 -1
- package/lib/bin/cli/sessionStore.js +2 -2
- package/lib/bin/cli/sessionStore.js.map +1 -1
- package/lib/bin/cli/ship.d.ts.map +1 -1
- package/lib/bin/cli/ship.js +15 -23
- package/lib/bin/cli/ship.js.map +1 -1
- package/lib/bin/cli/update.d.ts.map +1 -1
- package/lib/bin/cli/update.js +34 -28
- package/lib/bin/cli/update.js.map +1 -1
- package/lib/bin/cli/updatePlan.d.ts +30 -0
- package/lib/bin/cli/updatePlan.d.ts.map +1 -0
- package/lib/bin/cli/updatePlan.js +50 -0
- package/lib/bin/cli/updatePlan.js.map +1 -0
- package/lib/bin/cli/utils.d.ts +3 -0
- package/lib/bin/cli/utils.d.ts.map +1 -1
- package/lib/bin/cli/utils.js +7 -23
- package/lib/bin/cli/utils.js.map +1 -1
- package/lib/bin/cli/validate.d.ts.map +1 -1
- package/lib/bin/cli/validate.js +3 -25
- package/lib/bin/cli/validate.js.map +1 -1
- package/lib/bin/cli/watch.d.ts.map +1 -1
- package/lib/bin/cli/watch.js +3 -12
- package/lib/bin/cli/watch.js.map +1 -1
- package/lib/bin/forge-agent.js +10 -22
- package/lib/bin/forge-agent.js.map +1 -1
- package/lib/server/app.d.ts +24 -0
- package/lib/server/app.d.ts.map +1 -0
- package/lib/server/app.js +58 -0
- package/lib/server/app.js.map +1 -0
- package/lib/server/forge/analyses/analysisStore.d.ts +53 -0
- package/lib/server/forge/analyses/analysisStore.d.ts.map +1 -0
- package/lib/server/forge/analyses/analysisStore.js +77 -0
- package/lib/server/forge/analyses/analysisStore.js.map +1 -0
- package/lib/server/forge/auth/apiTokens.d.ts +34 -0
- package/lib/server/forge/auth/apiTokens.d.ts.map +1 -0
- package/lib/server/forge/auth/apiTokens.js +44 -0
- package/lib/server/forge/auth/apiTokens.js.map +1 -0
- package/lib/server/forge/auth/auditLog.d.ts +36 -0
- package/lib/server/forge/auth/auditLog.d.ts.map +1 -0
- package/lib/server/forge/auth/auditLog.js +56 -0
- package/lib/server/forge/auth/auditLog.js.map +1 -0
- package/lib/server/forge/auth/authConfig.d.ts +13 -0
- package/lib/server/forge/auth/authConfig.d.ts.map +1 -0
- package/lib/server/forge/auth/authConfig.js +37 -0
- package/lib/server/forge/auth/authConfig.js.map +1 -0
- package/lib/server/forge/auth/createAuth.d.ts +130 -0
- package/lib/server/forge/auth/createAuth.d.ts.map +1 -0
- package/lib/server/forge/auth/createAuth.js +387 -0
- package/lib/server/forge/auth/createAuth.js.map +1 -0
- package/lib/server/forge/auth/middleware.d.ts +10 -0
- package/lib/server/forge/auth/middleware.d.ts.map +1 -0
- package/lib/server/forge/auth/middleware.js +22 -0
- package/lib/server/forge/auth/middleware.js.map +1 -0
- package/lib/server/forge/auth/oidc.d.ts +38 -0
- package/lib/server/forge/auth/oidc.d.ts.map +1 -0
- package/lib/server/forge/auth/oidc.js +63 -0
- package/lib/server/forge/auth/oidc.js.map +1 -0
- package/lib/server/forge/auth/passwords.d.ts +7 -0
- package/lib/server/forge/auth/passwords.d.ts.map +1 -0
- package/lib/server/forge/auth/passwords.js +28 -0
- package/lib/server/forge/auth/passwords.js.map +1 -0
- package/lib/server/forge/auth/resetPassword.d.ts +14 -0
- package/lib/server/forge/auth/resetPassword.d.ts.map +1 -0
- package/lib/server/forge/auth/resetPassword.js +29 -0
- package/lib/server/forge/auth/resetPassword.js.map +1 -0
- package/lib/server/forge/auth/roles.d.ts +7 -0
- package/lib/server/forge/auth/roles.d.ts.map +1 -0
- package/lib/server/forge/auth/roles.js +15 -0
- package/lib/server/forge/auth/roles.js.map +1 -0
- package/lib/server/forge/auth/sessions.d.ts +27 -0
- package/lib/server/forge/auth/sessions.d.ts.map +1 -0
- package/lib/server/forge/auth/sessions.js +37 -0
- package/lib/server/forge/auth/sessions.js.map +1 -0
- package/lib/server/forge/auth/testHarness.d.ts +104 -0
- package/lib/server/forge/auth/testHarness.d.ts.map +1 -0
- package/lib/server/forge/auth/testHarness.js +53 -0
- package/lib/server/forge/auth/testHarness.js.map +1 -0
- package/lib/server/forge/auth/tokenHash.d.ts +3 -0
- package/lib/server/forge/auth/tokenHash.d.ts.map +1 -0
- package/lib/server/forge/auth/tokenHash.js +4 -0
- package/lib/server/forge/auth/tokenHash.js.map +1 -0
- package/lib/server/forge/auth/users.d.ts +51 -0
- package/lib/server/forge/auth/users.d.ts.map +1 -0
- package/lib/server/forge/auth/users.js +118 -0
- package/lib/server/forge/auth/users.js.map +1 -0
- package/lib/server/forge/core/ForgeAgent.d.ts +43 -1
- package/lib/server/forge/core/ForgeAgent.d.ts.map +1 -1
- package/lib/server/forge/core/ForgeAgent.js +209 -61
- package/lib/server/forge/core/ForgeAgent.js.map +1 -1
- package/lib/server/forge/core/agentClient.d.ts +7 -23
- package/lib/server/forge/core/agentClient.d.ts.map +1 -1
- package/lib/server/forge/core/agentClient.js +10 -25
- package/lib/server/forge/core/agentClient.js.map +1 -1
- package/lib/server/forge/core/claudeClient.d.ts +24 -1
- package/lib/server/forge/core/claudeClient.d.ts.map +1 -1
- package/lib/server/forge/core/claudeClient.js +25 -7
- package/lib/server/forge/core/claudeClient.js.map +1 -1
- package/lib/server/forge/core/configStore.d.ts +11 -1
- package/lib/server/forge/core/configStore.d.ts.map +1 -1
- package/lib/server/forge/core/configStore.js +33 -2
- package/lib/server/forge/core/configStore.js.map +1 -1
- package/lib/server/forge/core/dependencyPaths.d.ts +19 -0
- package/lib/server/forge/core/dependencyPaths.d.ts.map +1 -0
- package/lib/server/forge/core/dependencyPaths.js +32 -0
- package/lib/server/forge/core/dependencyPaths.js.map +1 -0
- package/lib/server/forge/core/evidence.d.ts +43 -0
- package/lib/server/forge/core/evidence.d.ts.map +1 -0
- package/lib/server/forge/core/evidence.js +108 -0
- package/lib/server/forge/core/evidence.js.map +1 -0
- package/lib/server/forge/core/jobs.d.ts +2 -0
- package/lib/server/forge/core/jobs.d.ts.map +1 -1
- package/lib/server/forge/core/jobs.js +8 -0
- package/lib/server/forge/core/jobs.js.map +1 -1
- package/lib/server/forge/core/localRepo.d.ts +31 -10
- package/lib/server/forge/core/localRepo.d.ts.map +1 -1
- package/lib/server/forge/core/localRepo.js +48 -82
- package/lib/server/forge/core/localRepo.js.map +1 -1
- package/lib/server/forge/core/prompts.d.ts +3 -1
- package/lib/server/forge/core/prompts.d.ts.map +1 -1
- package/lib/server/forge/core/prompts.js +14 -5
- package/lib/server/forge/core/prompts.js.map +1 -1
- package/lib/server/forge/core/providers/azuredevops.d.ts +4 -2
- package/lib/server/forge/core/providers/azuredevops.d.ts.map +1 -1
- package/lib/server/forge/core/providers/azuredevops.js +12 -2
- package/lib/server/forge/core/providers/azuredevops.js.map +1 -1
- package/lib/server/forge/core/providers/base.d.ts +29 -4
- package/lib/server/forge/core/providers/base.d.ts.map +1 -1
- package/lib/server/forge/core/providers/base.js +21 -3
- package/lib/server/forge/core/providers/base.js.map +1 -1
- package/lib/server/forge/core/providers/github.d.ts +7 -7
- package/lib/server/forge/core/providers/github.d.ts.map +1 -1
- package/lib/server/forge/core/providers/github.js +13 -4
- package/lib/server/forge/core/providers/github.js.map +1 -1
- package/lib/server/forge/core/providers/gitlab.d.ts +20 -3
- package/lib/server/forge/core/providers/gitlab.d.ts.map +1 -1
- package/lib/server/forge/core/providers/gitlab.js +65 -5
- package/lib/server/forge/core/providers/gitlab.js.map +1 -1
- package/lib/server/forge/core/providers/types.d.ts +20 -0
- package/lib/server/forge/core/providers/types.d.ts.map +1 -1
- package/lib/server/forge/core/secretBox.d.ts +17 -0
- package/lib/server/forge/core/secretBox.d.ts.map +1 -0
- package/lib/server/forge/core/secretBox.js +74 -0
- package/lib/server/forge/core/secretBox.js.map +1 -0
- package/lib/server/forge/core/types.d.ts +7 -1
- package/lib/server/forge/core/types.d.ts.map +1 -1
- package/lib/server/forge/core/validator/clone.d.ts +5 -4
- package/lib/server/forge/core/validator/clone.d.ts.map +1 -1
- package/lib/server/forge/core/validator/clone.js +6 -86
- package/lib/server/forge/core/validator/clone.js.map +1 -1
- package/lib/server/forge/core/validator/cmd.d.ts.map +1 -1
- package/lib/server/forge/core/validator/cmd.js +4 -1
- package/lib/server/forge/core/validator/cmd.js.map +1 -1
- package/lib/server/forge/core/validator/projectType.d.ts.map +1 -1
- package/lib/server/forge/core/validator/projectType.js +2 -1
- package/lib/server/forge/core/validator/projectType.js.map +1 -1
- package/lib/server/forge/core/validator/runners/node.d.ts.map +1 -1
- package/lib/server/forge/core/validator/runners/node.js +6 -2
- package/lib/server/forge/core/validator/runners/node.js.map +1 -1
- package/lib/server/forge/core/validator.d.ts.map +1 -1
- package/lib/server/forge/core/validator.js +28 -22
- package/lib/server/forge/core/validator.js.map +1 -1
- package/lib/server/forge/core/workspaces.d.ts +70 -0
- package/lib/server/forge/core/workspaces.d.ts.map +1 -0
- package/lib/server/forge/core/workspaces.js +348 -0
- package/lib/server/forge/core/workspaces.js.map +1 -0
- package/lib/server/forge/db/database.d.ts +22 -0
- package/lib/server/forge/db/database.d.ts.map +1 -0
- package/lib/server/forge/db/database.js +71 -0
- package/lib/server/forge/db/database.js.map +1 -0
- package/lib/server/forge/db/migrations.d.ts +11 -0
- package/lib/server/forge/db/migrations.d.ts.map +1 -0
- package/lib/server/forge/db/migrations.js +87 -0
- package/lib/server/forge/db/migrations.js.map +1 -0
- package/lib/server/forge/routes.d.ts.map +1 -1
- package/lib/server/forge/routes.js +413 -163
- package/lib/server/forge/routes.js.map +1 -1
- package/lib/server/forge/sse.d.ts +10 -0
- package/lib/server/forge/sse.d.ts.map +1 -0
- package/lib/server/forge/sse.js +22 -0
- package/lib/server/forge/sse.js.map +1 -0
- package/lib/server/spa.d.ts +12 -0
- package/lib/server/spa.d.ts.map +1 -0
- package/lib/server/spa.js +23 -0
- package/lib/server/spa.js.map +1 -0
- package/lib/server.js +9 -24
- package/lib/server.js.map +1 -1
- package/lib/shared/events.d.ts +3 -0
- package/lib/shared/events.d.ts.map +1 -1
- package/lib/shared/events.js +3 -0
- package/lib/shared/events.js.map +1 -1
- package/lib/shared/packageRoot.d.ts +11 -0
- package/lib/shared/packageRoot.d.ts.map +1 -0
- package/lib/shared/packageRoot.js +23 -0
- package/lib/shared/packageRoot.js.map +1 -0
- package/lib/shared/project.d.ts +3 -0
- package/lib/shared/project.d.ts.map +1 -0
- package/lib/shared/project.js +3 -0
- package/lib/shared/project.js.map +1 -0
- package/package.json +23 -10
- package/lib/bin/cli/improve.d.ts +0 -2
- package/lib/bin/cli/improve.d.ts.map +0 -1
- package/lib/bin/cli/improve.js +0 -189
- package/lib/bin/cli/improve.js.map +0 -1
- package/lib/bin/cli/stats.d.ts +0 -2
- package/lib/bin/cli/stats.d.ts.map +0 -1
- package/lib/bin/cli/stats.js +0 -78
- package/lib/bin/cli/stats.js.map +0 -1
- package/lib/server/forge/core/k8s.d.ts +0 -18
- package/lib/server/forge/core/k8s.d.ts.map +0 -1
- package/lib/server/forge/core/k8s.js +0 -49
- package/lib/server/forge/core/k8s.js.map +0 -1
- package/lib/server/forge/core/openclawClient.d.ts +0 -61
- package/lib/server/forge/core/openclawClient.d.ts.map +0 -1
- package/lib/server/forge/core/openclawClient.js +0 -111
- package/lib/server/forge/core/openclawClient.js.map +0 -1
- package/lib/server/forge/core/quotaClient.d.ts +0 -48
- package/lib/server/forge/core/quotaClient.d.ts.map +0 -1
- package/lib/server/forge/core/quotaClient.js +0 -88
- package/lib/server/forge/core/quotaClient.js.map +0 -1
- package/lib/server/forge/core/selfImprove.d.ts +0 -43
- package/lib/server/forge/core/selfImprove.d.ts.map +0 -1
- package/lib/server/forge/core/selfImprove.js +0 -372
- package/lib/server/forge/core/selfImprove.js.map +0 -1
- package/lib/server/forge/core/telemetry.d.ts +0 -100
- package/lib/server/forge/core/telemetry.d.ts.map +0 -1
- package/lib/server/forge/core/telemetry.js +0 -273
- package/lib/server/forge/core/telemetry.js.map +0 -1
- package/scripts/fix-shebang.cjs +0 -11
- package/scripts/inject-readme.cjs +0 -23
- 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 @@
|
|
|
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 @@
|
|
|
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():
|
|
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 {};
|