feat: zitadel token auth middleware

This commit is contained in:
2026-06-10 07:53:42 +00:00
parent 50173a3809
commit 394bceca5f
3 changed files with 142 additions and 0 deletions

65
tests/auth.test.ts Normal file
View File

@@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import { SignJWT, generateKeyPair } from "jose";
import { makeVerifier } from "../server/utils/auth";
const ISS = "https://auth.kuns.dev";
async function setup() {
const { publicKey, privateKey } = await generateKeyPair("RS256");
const verify = makeVerifier({
issuer: ISS,
audiences: ["aud-web", "proj-1"],
allowedSubs: ["owner-1"],
keyResolver: async () => publicKey,
});
const sign = (claims: Record<string, unknown>) =>
new SignJWT(claims)
.setProtectedHeader({ alg: "RS256" })
.setIssuer(ISS)
.setIssuedAt()
.setExpirationTime("5m")
.sign(privateKey);
return { verify, sign };
}
describe("token verification", () => {
it("accepts an owner token with a valid audience", async () => {
const { verify, sign } = await setup();
const t = await sign({ sub: "owner-1", aud: ["aud-web"] });
await expect(verify(t)).resolves.toMatchObject({ sub: "owner-1" });
});
it("accepts when aud is a single string in the allowed set", async () => {
const { verify, sign } = await setup();
const t = await sign({ sub: "owner-1", aud: "proj-1" });
await expect(verify(t)).resolves.toMatchObject({ sub: "owner-1" });
});
it("rejects a non-owner sub", async () => {
const { verify, sign } = await setup();
const t = await sign({ sub: "intruder", aud: ["aud-web"] });
await expect(verify(t)).rejects.toThrow();
});
it("rejects a token with no accepted audience", async () => {
const { verify, sign } = await setup();
const t = await sign({ sub: "owner-1", aud: ["other"] });
await expect(verify(t)).rejects.toThrow();
});
it("rejects a wrong issuer", async () => {
const { publicKey, privateKey } = await generateKeyPair("RS256");
const verify = makeVerifier({
issuer: ISS,
audiences: ["aud-web"],
allowedSubs: ["owner-1"],
keyResolver: async () => publicKey,
});
const t = await new SignJWT({ sub: "owner-1", aud: ["aud-web"] })
.setProtectedHeader({ alg: "RS256" })
.setIssuer("https://evil.example")
.setExpirationTime("5m")
.sign(privateKey);
await expect(verify(t)).rejects.toThrow();
});
});