-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathproxy-self-hosted.test.ts
More file actions
81 lines (66 loc) · 2.4 KB
/
Copy pathproxy-self-hosted.test.ts
File metadata and controls
81 lines (66 loc) · 2.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { NextRequest } from "next/server";
import { describe, expect, it, vi } from "vitest";
import { proxy } from "../../proxy";
vi.mock("@cap/database", () => ({
db: () => {
throw new Error("Database should not be reached on self-hosted routes");
},
}));
vi.mock("@cap/database/schema", () => ({ organizations: {} }));
vi.mock("@cap/env", () => ({
buildEnv: { NEXT_PUBLIC_IS_CAP: "false" },
serverEnv: () => ({
WEB_URL: "https://cap.example.com",
VERCEL_URL_HOST: undefined,
VERCEL_BRANCH_URL_HOST: undefined,
VERCEL_PROJECT_PRODUCTION_URL_HOST: undefined,
}),
}));
const request = (path: string) =>
proxy(new NextRequest(`https://cap.example.com${path}`));
const expectServed = async (path: string) => {
const response = await request(path);
expect(response.status).toBe(200);
expect(response.headers.get("location")).toBeNull();
};
const expectLoginRedirect = async (path: string) => {
const response = await request(path);
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe(
"https://cap.example.com/login",
);
};
describe("self-hosted proxy routes", () => {
it("allows browser-based CLI authorization pages", () => {
const source = readFileSync(join(process.cwd(), "proxy.ts"), "utf8");
expect(source).toContain('path.startsWith("/cli/")');
});
it.each([
"/logos/browsers/google-chrome.svg",
"/illustrations/app.webp",
"/sounds/start-recording.ogg",
"/rive/main.riv",
"/fonts/Geist-Regular.woff2",
"/site.webmanifest",
"/.well-known/atproto-did",
])("serves the public asset %s instead of redirecting", (path) =>
expectServed(path),
);
it("still redirects page routes to /login", () =>
expectLoginRedirect("/pricing"));
it("still redirects extension-suffixed route handlers to /login", () =>
expectLoginRedirect("/install-cli.sh"));
it("does not let a missing file through", () =>
expectLoginRedirect("/logos/missing.svg"));
it("does not let a directory through", () => expectLoginRedirect("/logos"));
it("rejects path traversal out of public/", () =>
expectLoginRedirect("/logos/..%2F..%2Fproxy.ts"));
it.each(["/%00", "/favicon.ico/nested.svg", `/${"a".repeat(5000)}.svg`])(
"treats a filesystem lookup failure for %s as not an asset",
(path) => expectLoginRedirect(path),
);
it("does not treat a share link as an asset", () =>
expectServed("/s/video123"));
});