feat: add Keycloak authentication

This commit is contained in:
2026-08-19 12:11:18 +08:00
parent bdd5eff776
commit d08cf2abc1
27 changed files with 533 additions and 128 deletions
+24 -2
View File
@@ -6,6 +6,9 @@ describe("runtime frontend configuration", () => {
it("uses container-provided values without relying on Vite build variables", () => {
expect(
parseRuntimeConfig({
TJWATER_AUTH_MODE: "required",
TJWATER_KEYCLOAK_ISSUER: "https://auth.example.test/realms/tjwater/",
TJWATER_KEYCLOAK_CLIENT_ID: "next-tjwater",
TJWATER_MAPBOX_ACCESS_TOKEN: "token",
TJWATER_MAP_URL: "https://maps.example.test/geoserver",
TJWATER_GEOSERVER_WORKSPACE: "project-a",
@@ -14,6 +17,9 @@ describe("runtime frontend configuration", () => {
TJWATER_ENABLE_MSW: "false"
})
).toEqual({
TJWATER_AUTH_MODE: "required",
TJWATER_KEYCLOAK_ISSUER: "https://auth.example.test/realms/tjwater",
TJWATER_KEYCLOAK_CLIENT_ID: "next-tjwater",
TJWATER_MAPBOX_ACCESS_TOKEN: "token",
TJWATER_MAP_URL: "https://maps.example.test/geoserver",
TJWATER_GEOSERVER_WORKSPACE: "project-a",
@@ -24,12 +30,18 @@ describe("runtime frontend configuration", () => {
});
it("applies typed defaults for optional feature flags", () => {
expect(parseRuntimeConfig({})).toMatchObject({
expect(parseRuntimeConfig({ TJWATER_AUTH_MODE: "disabled" })).toMatchObject({
TJWATER_AUTH_MODE: "disabled",
TJWATER_KEYCLOAK_CLIENT_ID: "next-tjwater",
TJWATER_ENABLE_DEV_PANEL: false,
TJWATER_ENABLE_MSW: false
});
});
it("requires a Realm issuer when authentication is enabled", () => {
expect(() => parseRuntimeConfig({ TJWATER_AUTH_MODE: "required" })).toThrow();
});
it("rejects invalid runtime URLs before the application starts", () => {
expect(() => parseRuntimeConfig({ TJWATER_MAP_URL: "not-a-url" })).toThrow();
});
@@ -39,6 +51,16 @@ describe("runtime frontend configuration", () => {
["TJWATER_AGENT_API_BASE_URL", "https://user:secret@agent.example.test"],
["TJWATER_AGENT_API_BASE_URL", "https://agent.example.test/#secret"]
])("rejects unsafe browser runtime address %s", (key, value) => {
expect(() => parseRuntimeConfig({ [key]: value })).toThrow();
expect(() => parseRuntimeConfig({ TJWATER_AUTH_MODE: "disabled", [key]: value })).toThrow();
});
it("rejects a Keycloak URL that does not identify a Realm", () => {
expect(() =>
parseRuntimeConfig({
TJWATER_AUTH_MODE: "required",
TJWATER_KEYCLOAK_ISSUER: "https://auth.example.test/",
TJWATER_KEYCLOAK_CLIENT_ID: "next-tjwater"
})
).toThrow();
});
});