fabro/apps/fabro-web/app/mode.ts
Bryan Helmkamp 75f8ed845b
fix(install): harden web wizard against review findings
Tighten the browser-based install flow after correctness and adversarial
review, without changing the external wizard shape.

- Persist the actual bind in server.listen, not the canonical URL
- Reject concurrent /install/finish and rapid GitHub App retries
- Keep the prior GitHub Token strategy until App callback succeeds
- Recover from poisoned install locks instead of propagating panics
- Rollback both settings and vault on failed persistence
- Redirect GitHub callback errors back into the wizard UI
- Validate LLM keys via /models probe instead of a billed generate()
- Reject canonical URLs with trailing slash, path, query, or fragment
- Accept any valid install-token source, not just the first present one
- Redact the install token in structured logs
- Assert install-mode SPA marker injection at startup
- Warn on suspected concurrent operators via UA + X-Forwarded-For
- Add component-level test for the GitHub callback error banner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:43:00 -04:00

56 lines
2 KiB
TypeScript

export type FabroMode = "normal" | "install";
export function resolveFabroMode(value: unknown): FabroMode {
return value === "install" ? "install" : "normal";
}
export function consumeInstallTokenFromUrl(url: string): {
token: string | null;
sanitizedUrl: string;
} {
const parsed = new URL(url);
const token = parsed.searchParams.get("token");
if (!token) {
return { token: null, sanitizedUrl: parsed.toString() };
}
parsed.searchParams.delete("token");
return {
token,
sanitizedUrl: parsed.toString(),
};
}
const INSTALL_GITHUB_ERROR_MESSAGES: Record<string, string> = {
"missing-install-github-app-state":
"GitHub App setup could not resume because the install state was missing. Continue again to create a fresh handoff.",
"missing-install-github-app-code":
"GitHub did not return the manifest conversion code Fabro needed. Continue again to retry the GitHub App handoff.",
"expired-install-github-app-state":
"GitHub App setup took too long and the temporary install state expired. Continue again to create a fresh handoff.",
"invalid-install-github-app-state":
"GitHub App setup returned with the wrong install state. Continue again to restart the secure handoff.",
"github-app-manifest-conversion-failed":
"GitHub App setup failed before Fabro could save the app credentials. Continue again to retry the callback.",
};
export function shouldConsumeInstallGithubErrorForPath(pathname: string): boolean {
return pathname === "/install/github" || pathname.startsWith("/install/github/");
}
export function consumeInstallGithubErrorFromUrl(url: string): {
error: string | null;
sanitizedUrl: string;
} {
const parsed = new URL(url);
const errorCode = parsed.searchParams.get("error");
if (!errorCode) {
return { error: null, sanitizedUrl: parsed.toString() };
}
parsed.searchParams.delete("error");
return {
error: INSTALL_GITHUB_ERROR_MESSAGES[errorCode] ?? "GitHub App setup failed. Continue again to retry.",
sanitizedUrl: parsed.toString(),
};
}