124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { isTrustedAdminMutationRequest } from "../src/lib/admin/auth-policy.ts";
|
|
|
|
const adminOrigin = "https://admin.staging.jyotisha.chat";
|
|
const userOrigin = "https://staging.jyotisha.chat";
|
|
|
|
function request(
|
|
url: string,
|
|
headers: HeadersInit,
|
|
): Request {
|
|
return new Request(url, { method: "POST", headers });
|
|
}
|
|
|
|
test("trusted proxy admin origin accepts the configured host and protocol", () => {
|
|
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https",
|
|
});
|
|
|
|
assert.equal(isTrustedAdminMutationRequest(proxied, adminOrigin), true);
|
|
});
|
|
|
|
test("configured admin origin rejects wrong browser origins", () => {
|
|
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
|
origin: "https://evil.example",
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https",
|
|
});
|
|
|
|
assert.equal(isTrustedAdminMutationRequest(proxied, adminOrigin), false);
|
|
});
|
|
|
|
test("ordinary staging host cannot call admin mutations", () => {
|
|
const userHost = request(`${userOrigin}/api/admin/models`, {
|
|
origin: userOrigin,
|
|
host: "staging.jyotisha.chat",
|
|
"x-forwarded-host": "staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https",
|
|
});
|
|
const forgedForwardedHost = request(`${userOrigin}/api/admin/models`, {
|
|
origin: adminOrigin,
|
|
host: "staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https",
|
|
});
|
|
|
|
assert.equal(isTrustedAdminMutationRequest(userHost, adminOrigin), false);
|
|
assert.equal(isTrustedAdminMutationRequest(forgedForwardedHost, adminOrigin), false);
|
|
});
|
|
|
|
test("malformed or ambiguous forwarded origins fail closed", () => {
|
|
const cases: HeadersInit[] = [
|
|
{
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat, evil.example",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
{
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https, http",
|
|
},
|
|
{
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat/path",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
{
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "",
|
|
"x-forwarded-proto": "https",
|
|
},
|
|
{
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
},
|
|
];
|
|
for (const headers of cases) {
|
|
assert.equal(
|
|
isTrustedAdminMutationRequest(
|
|
request("http://admin.staging.jyotisha.chat/api/admin/models", headers),
|
|
adminOrigin,
|
|
),
|
|
false,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("direct same-origin requests retain the legacy fallback when no admin origin is configured", () => {
|
|
const direct = request("https://admin.example/api/admin/models", {
|
|
origin: "https://admin.example",
|
|
});
|
|
|
|
assert.equal(isTrustedAdminMutationRequest(direct), true);
|
|
});
|
|
|
|
test("invalid configured admin origins fail closed", () => {
|
|
const proxied = request("http://admin.staging.jyotisha.chat/api/admin/models", {
|
|
origin: adminOrigin,
|
|
host: "admin.staging.jyotisha.chat",
|
|
"x-forwarded-host": "admin.staging.jyotisha.chat",
|
|
"x-forwarded-proto": "https",
|
|
});
|
|
|
|
for (const configured of [
|
|
"not-an-origin",
|
|
`${adminOrigin}/path`,
|
|
"ftp://admin.staging.jyotisha.chat",
|
|
"http://admin.staging.jyotisha.chat",
|
|
]) {
|
|
assert.equal(isTrustedAdminMutationRequest(proxied, configured), false);
|
|
}
|
|
});
|