Jiseoup/showmycodePublic
EN|KO
  • 코드
  • 커밋
  • 풀 리퀘스트
← 목록으로

Merge pull request #20 from Jiseoup/fix/proxy-auth-bypass

fix: close share-token bypass in GitHub proxy route and proxy matcher

JiseoupJISUB LIM · 2026년 6월 11일2fce610

변경된 파일3개+18 -40

변경된 파일

+18 -40 · 3개

@@ -1,34 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { getAllowedRepos } from "@/lib/github";
-
-const BASE = "https://api.github.com";
-const PAT = process.env.GITHUB_PAT!;
-
-// This route proxies all GitHub API requests so the PAT never reaches the client.
-// It validates every request against the GITHUB_REPOS allowlist before forwarding.
-export async function GET(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
- const { path } = await params;
- const ghPath = path.join("/");
-
- // Extract owner and repo from repos/{owner}/{repo}/... and validate against allowlist.
- const match = ghPath.match(/^repos\/([^/]+)\/([^/]+)/);
- if (match) {
- const [, owner, repo] = match;
- const allowed = getAllowedRepos().some((r) => r.owner === owner && r.repo === repo);
- if (!allowed) return NextResponse.json({ message: "Not Found" }, { status: 404 });
- }
-
- const search = req.nextUrl.search;
-
- const res = await fetch(`${BASE}/${ghPath}${search}`, {
- headers: {
- Authorization: `Bearer ${PAT}`,
- Accept: "application/vnd.github+json",
- "X-GitHub-Api-Version": "2022-11-28",
- },
- next: { revalidate: 60 },
- });
-
- const data = await res.json();
- return NextResponse.json(data, { status: res.status });
-}
@@ -65,7 +65,7 @@ All pages are under `app/[lang]/` for internationalization (KO/EN). `proxy.ts` (
```
/[lang]/ → Repository listing
-/[lang]/repository/[owner]/[repo]/ → File tree + code viewer
+/[lang]/repository/[owner]/[repo]/ → File tree + code viewer (?path= selects a file, ?branch= switches branch via BranchSelector)
/[lang]/repository/[owner]/[repo]/commits/ → Commit history (paginated, default 20/page; configurable via COMMITS_PER_PAGE)
/[lang]/repository/[owner]/[repo]/commits/[sha] → Commit detail (files changed + diff)
/[lang]/repository/[owner]/[repo]/pulls/ → Pull request list (paginated, default 10/page; configurable via PULLS_PER_PAGE)
@@ -83,17 +83,19 @@ Subsequent visits reuse the cookie automatically. An invalid/missing token redir
If `SHARE_TOKEN` is not set, all access is blocked. The token is never exposed to the client.
+Note: the `proxy.ts` matcher excludes `/api/*`, framework internals (`_next/static`, `_next/image`), and known static assets by name (`favicon.ico`, `icon.svg`) — API routes are NOT covered by the share-token check and must enforce their own auth. Currently the only API route is `/api/auth`, which is intentionally public (it is the token-entry endpoint). The matcher deliberately does NOT exclude dotted paths (`.*\..*`): repository names can contain dots (e.g. `next.js`), and a blanket dot-exclusion would let those repo pages bypass auth. If you add files to `public/`, add them to the matcher exclusion list or they will hit the auth gate.
+
### GitHub API Security Model
-The GitHub PAT never reaches the client. All GitHub API calls go through a server-side proxy:
+The GitHub PAT never reaches the client. All GitHub API calls happen server-side — pages are Server Components that call the typed fetch helpers in `lib/github.ts` directly:
```
-Client → /api/github/[...path] → GitHub API (with PAT from env)
+Server Component → lib/github.ts → GitHub API (with PAT from env)
```
-The API route (`app/api/github/[...path]/route.ts`) validates that requested repos are in the `GITHUB_REPOS` allowlist before proxying.
+There is no client-facing GitHub proxy route; do not add one without share-token auth (`proxy.ts` does not cover `/api/*` — see Access Control). The repository layout (`app/[lang]/repository/[owner]/[repo]/layout.tsx`) validates `owner`/`repo` against the `GITHUB_REPOS` allowlist (`getAllowedRepos()`) and 404s anything else.
-`lib/github.ts` provides typed fetch helpers used by server components and the API route.
+GitHub responses are cached for 60 seconds via `next: { revalidate: 60 }` in `ghFetch` (`lib/github.ts`) — data shown to viewers may be up to a minute stale.
### Key Patterns
@@ -74,6 +74,16 @@ export function proxy(request: NextRequest) {
return NextResponse.redirect(new URL("/unauthorized", request.url));
}
+/**
+ * Run the proxy on every page route, excluding only framework internals and named static assets.
+ *
+ * Dotted paths are intentionally not excluded:
+ * repository names can contain dots (e.g. `next.js`),
+ * so a blanket `.*\..*` rule would let those repo pages bypass the share-token check.
+ *
+ * When adding files to `public/` (e.g. robots.txt, og images),
+ * list them here — otherwise they hit the auth gate and locale redirect.
+ */
export const config = {
- matcher: ["/((?!_next|api|.*\\..*).*)"],
+ matcher: ["/((?!_next/static|_next/image|api|favicon.ico|icon.svg).*)"],
};