Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

109 lines · 5.0 KBRaw History
1/** @jsxImportSource hono/jsx */
2import { Hono } from "hono";
3import type { Env } from "../env.js";
4import type { AppVars } from "../auth/principal.js";
5import { registerAccount } from "./account.js";
6import { registerAccountLifecycle } from "./account-lifecycle.js";
7import { registerTasks } from "../agentic/tasks-web.js";
8import { registerOrgAgents } from "../agentic/org-web.js";
9import { registerReviews } from "../agentic/review-web.js";
10import { registerProject } from "../agentic/project-web.js";
11import { registerTerminals } from "../agentic/terminal-web.js";
12import { registerMailWeb } from "../mail/web.js";
13import { registerSecurityReports } from "./security-reports.js";
14import { registerContact } from "./contact.js";
15import { registerEarlyAccess } from "./early-access.js";
16import { registerStatus } from "./status.js";
17import { registerAuth } from "./auth.js";
18import { registerCliLogin } from "./cli-login.js";
19import { registerOAuth } from "./oauth.js";
20import { registerActions } from "./actions.js";
21import { registerCode } from "./code.js";
22import { registerCollab } from "./collab.js";
23import { registerReleases } from "./releases.js";
24import { registerRepoSettings } from "./repo-settings.js";
25import { registerAgentSetup } from "./agent-setup.js";
26import { registerMergeOnGreen } from "./merge-on-green.js";
27import { registerImports } from "./imports.js";
28import { registerSeo } from "./seo.js";
29import { registerSite } from "./site.js";
30import { errorPage } from "./ui.js";
31import { RESERVED_LOGINS } from "../lib/util.js";
32import { registerPackages } from "./packages.js";
33import { registerDocs } from "../docs/web.js";
34import { registerModeration } from "./moderation.js";
35import { registerApps } from "./apps.js";
36
37/**
38 * Server-rendered web UI. Mount at "/" after the API, Git and runner routes:
39 * it owns `/`, `/docs/*`, `/llms.txt`, `/login`, `/auth/*`, `/signin`, `/signin/*`, `/new`, `/settings/*`, `/notifications/*`, `/invite/*`, `/-/*`,
40 * `/:owner` and `/:owner/:repo/...` (including `/:owner/:repo/raw/*`).
41 *
42 * Expects the `principal` and `dir` variables to be set by an earlier middleware.
43 * Every POST is CSRF-checked (double-submit cookie, see ./csrf.ts).
44 */
45export const web = new Hono<{ Bindings: Env; Variables: AppVars }>();
46
47web.onError((err, c) => errorPage(c, err));
48
49/**
50 * A limited API token (read or write scope, or limited to some repositories) reaches the web only
51 * for repository pages (/:owner/:repo/...), where loadRepo applies its scope and repository limits as
52 * the API does. Everywhere else (the dashboard, the command palette, notifications, profiles,
53 * settings, site administration) it would see what the API refuses it, so those pages treat it as
54 * signed out. An unlimited admin token can already read all of that through the API.
55 */
56export function tokenMayUseWebPage(path: string): boolean {
57 const segs = path.split("/").filter(Boolean);
58 return segs.length >= 2 && !RESERVED_LOGINS.has(segs[0]!.toLowerCase()) && segs[1] !== "-";
59}
60
61web.use("*", async (c, next) => {
62 const p = c.get("principal");
63 const limited = p.type === "account" && p.via === "token" && (!!p.repoIds || !p.scopes.includes("admin"));
64 if (limited && !tokenMayUseWebPage(c.req.path)) c.set("principal", { type: "anonymous" });
65 await next();
66});
67
68// Browsers and crawlers ask for these on every origin; keep them off the /:owner route.
69registerSeo(web);
70// Public docs (/docs, /llms.txt, ...): before /:owner/:repo, which would otherwise match /docs/<page>.
71registerDocs(web);
72// The product, comparison and company pages (/features, /compare/github, ...) and /index.md: before /:owner.
73registerSite(web);
74
75// Order matters: fixed paths (/settings/tokens, /notifications/:id, …) must be
76// registered before the /:owner/:repo patterns they would otherwise match.
77registerAuth(web);
78registerCliLogin(web);
79registerOAuth(web);
80registerPackages(web);
81registerMailWeb(web);
82registerSecurityReports(web);
83registerEarlyAccess(web);
84registerContact(web);
85// /status: before the /:owner catch-all.
86registerStatus(web);
87// Before registerAccount: its /:owner catch-all would otherwise swallow /imports, /new/import and /settings/linked-accounts.
88registerImports(web);
89// Settings → Model access and /:owner/:repo/tasks: before the /:owner and /:owner/:repo catch-alls.
90registerTasks(web);
91registerOrgAgents(web);
92registerReviews(web);
93registerProject(web);
94registerTerminals(web);
95// Settings → Account and the account pages of site administration: fixed /settings paths, before /:owner.
96registerAccountLifecycle(web);
97// Settings → Apps and Installed apps, and /apps/:slug: fixed paths, before /:owner.
98registerApps(web);
99// /-/report and /:owner/:repo/comments/:id/...: before the /:owner and /:owner/:repo catch-alls.
100registerModeration(web);
101registerAccount(web);
102// Settings → Agent setup: also for people with write access, so before the admin-only settings pages.
103registerAgentSetup(web);
104registerMergeOnGreen(web);
105registerRepoSettings(web);
106registerActions(web);
107registerReleases(web);
108registerCollab(web);
109registerCode(web);