Files
NianAIGC/tests/auth-password-route.test.ts

98 lines
3.6 KiB
TypeScript

import { createSign, generateKeyPairSync, type KeyObject } from "node:crypto";
import { afterEach, describe, expect, it, vi } from "vitest";
import { clearJwksCacheForTests } from "@/lib/server/auth/jwt";
import { POST } from "@/app/api/auth/password/route";
type TestJwk = JsonWebKey & {
kid?: string;
alg?: string;
use?: string;
};
const baseEnv = {
ZHINIAN_AUTH_REQUIRED: "1",
ZHINIAN_AUTH_BASE_URL: "https://gateway.example.com/auth",
ZHINIAN_AUTH_CLIENT_ID: "agentbus-client",
ZHINIAN_AUTH_CLIENT_SECRET: "client-secret",
ZHINIAN_AUTH_SCOPE: "server",
ZHINIAN_AUTH_ISSUER: "https://pig4cloud.com",
ZHINIAN_AUTH_SESSION_SECRET: "test-session-secret-with-enough-entropy"
};
describe("password auth route AgentBus compatibility", () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
clearJwksCacheForTests();
});
it("encrypts password with the configured AES-CFB key and does not require captcha fields", async () => {
for (const [key, value] of Object.entries(baseEnv)) vi.stubEnv(key, value);
vi.stubEnv("ZHINIAN_AUTH_PASSWORD_ENC_KEY", "thanks,pig4cloud");
const { publicKey, privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const jwk = publicKey.export({ format: "jwk" }) as TestJwk;
jwk.kid = "agentbus-key";
const accessToken = signJwt({
iss: baseEnv.ZHINIAN_AUTH_ISSUER,
sub: "subject-42",
user_id: "remote-42",
username: "user@example.com",
client_id: baseEnv.ZHINIAN_AUTH_CLIENT_ID,
scope: baseEnv.ZHINIAN_AUTH_SCOPE,
exp: Math.floor(Date.now() / 1000) + 600,
iat: Math.floor(Date.now() / 1000) - 10,
nbf: Math.floor(Date.now() / 1000) - 10
}, privateKey, "agentbus-key");
const seenBodies: URLSearchParams[] = [];
vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url.endsWith("/oauth2/jwks")) {
return new Response(JSON.stringify({ keys: [jwk] }), { status: 200 });
}
if (url.endsWith("/oauth2/token")) {
seenBodies.push(new URLSearchParams(String(init?.body)));
return new Response(JSON.stringify({
access_token: accessToken,
refresh_token: "refresh-token-1",
token_type: "bearer",
expires_in: "3600"
}), { status: 200 });
}
return new Response("not found", { status: 404 });
});
const response = await POST(new Request("https://app.example.com/api/auth/password", {
method: "POST",
body: JSON.stringify({
username: "user@example.com",
password: "123456",
next: "/create"
})
}));
expect(response.status).toBe(200);
expect(seenBodies).toHaveLength(1);
expect(seenBodies[0].get("grant_type")).toBe("password");
expect(seenBodies[0].get("username")).toBe("user@example.com");
expect(seenBodies[0].get("password")).toBe("YehdBPev");
expect(seenBodies[0].has("code")).toBe(false);
expect(seenBodies[0].has("randomStr")).toBe(false);
});
});
function signJwt(payload: Record<string, unknown>, privateKey: KeyObject, kid: string): string {
const header = base64UrlJson({ alg: "RS256", typ: "JWT", kid });
const body = base64UrlJson(payload);
const signingInput = `${header}.${body}`;
const signer = createSign("RSA-SHA256");
signer.update(signingInput);
signer.end();
const signature = signer.sign(privateKey).toString("base64url");
return `${signingInput}.${signature}`;
}
function base64UrlJson(value: unknown): string {
return Buffer.from(JSON.stringify(value)).toString("base64url");
}