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