diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index dd16220c..2446daf9 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -73,7 +73,10 @@ const HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET = new Set( HEADLESS_LOGIN_ALLOWED_ALGORITHMS, ); -function formatHeadlessParsedKeyLabel(kid: string | undefined, index: number): string { +function formatHeadlessParsedKeyLabel( + kid: string | undefined, + index: number, +): string { const trimmedKid = kid?.trim(); if (trimmedKid) { return trimmedKid; @@ -302,7 +305,7 @@ function ClientGeneralPage() { headlessLoginEnabled && trimmedJwksUri !== "" && currentHeadlessJwksCache?.jwksUri === trimmedJwksUri - ? currentHeadlessJwksCache.parsedKeys ?? [] + ? (currentHeadlessJwksCache.parsedKeys ?? []) : []; const unsupportedParsedAlgorithms = parsedKeysForCurrentJwksUri .map((key, index) => ({ @@ -463,8 +466,7 @@ function ClientGeneralPage() { ? tokenEndpointAuthMethod : undefined, headless_jwks_uri: - clientType === "pkce" && - headlessLoginEnabled + clientType === "pkce" && headlessLoginEnabled ? trimmedJwksUri : undefined, }, @@ -1148,9 +1150,7 @@ function ClientGeneralPage() { type="button" size="sm" variant="outline" - onClick={() => - refreshHeadlessJwksCacheMutation.mutate() - } + onClick={() => refreshHeadlessJwksCacheMutation.mutate()} disabled={refreshHeadlessJwksCacheMutation.isPending} > {refreshHeadlessJwksCacheMutation.isPending @@ -1202,10 +1202,7 @@ function ClientGeneralPage() { "Status", )}

- + {currentHeadlessJwksCache.lastRefreshStatus || t("ui.common.unknown", "Unknown")} @@ -1237,7 +1234,9 @@ function ClientGeneralPage() { "Expires At", )}

-

{formatDateTime(currentHeadlessJwksCache.expiresAt)}

+

+ {formatDateTime(currentHeadlessJwksCache.expiresAt)} +

@@ -1247,9 +1246,7 @@ function ClientGeneralPage() { )}

- {formatDateTime( - currentHeadlessJwksCache.lastCheckedAt, - )} + {formatDateTime(currentHeadlessJwksCache.lastCheckedAt)}

@@ -1272,9 +1269,7 @@ function ClientGeneralPage() { "Consecutive Failures", )}

-

- {currentHeadlessJwksCache.consecutiveFailures ?? 0} -

+

{currentHeadlessJwksCache.consecutiveFailures ?? 0}

@@ -1346,101 +1341,104 @@ function ClientGeneralPage() {

{currentHeadlessJwksCache.parsedKeys?.length ? (
- {currentHeadlessJwksCache.parsedKeys.map((key, index) => { - const normalizedAlgorithm = key.alg?.trim() ?? ""; - const isMissingAlgorithm = - normalizedAlgorithm === ""; - const isUnsupportedAlgorithm = - !isMissingAlgorithm && - !HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has( - normalizedAlgorithm, - ); + {currentHeadlessJwksCache.parsedKeys.map( + (key, index) => { + const normalizedAlgorithm = key.alg?.trim() ?? ""; + const isMissingAlgorithm = + normalizedAlgorithm === ""; + const isUnsupportedAlgorithm = + !isMissingAlgorithm && + !HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has( + normalizedAlgorithm, + ); - return ( -
-
-
-

- KID -

-

- {key.kid || "-"} -

-
-
-

- KTY -

-

- {key.kty || "-"} -

-
-
-

- USE -

-

- {key.use || "-"} -

-
-
-

- ALG -

-

+

+
+

+ KID +

+

+ {key.kid || "-"} +

+
+
+

+ KTY +

+

+ {key.kty || "-"} +

+
+
+

+ USE +

+

+ {key.use || "-"} +

+
+
+

+ ALG +

+

+ {key.alg || + t( + "msg.dev.clients.general.public_key.cache.missing_algorithm_badge", + "알고리즘 미선언", + )} +

+ {isMissingAlgorithm && ( +

+ {t( + "msg.dev.clients.general.public_key.cache.missing_algorithm_reason", + "이 키는 `alg`가 비어 있어서 저장할 수 없습니다.", + )} +

+ )} + {isUnsupportedAlgorithm && ( +

+ {t( + "msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason", + "이 알고리즘은 Headless Login에서 지원되지 않습니다.", + )} +

+ )} +
+
+
+

+ {t( + "ui.dev.clients.general.public_key.cache.parsed_key_n", + "N", )} - > - {key.alg || - t( - "msg.dev.clients.general.public_key.cache.missing_algorithm_badge", - "알고리즘 미선언", - )}

- {isMissingAlgorithm && ( -

- {t( - "msg.dev.clients.general.public_key.cache.missing_algorithm_reason", - "이 키는 `alg`가 비어 있어서 저장할 수 없습니다.", - )} -

- )} - {isUnsupportedAlgorithm && ( -

- {t( - "msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason", - "이 알고리즘은 Headless Login에서 지원되지 않습니다.", - )} -

- )} +

+ {key.n || "-"} +

-
-

- {t( - "ui.dev.clients.general.public_key.cache.parsed_key_n", - "N", - )} -

-

- {key.n || "-"} -

-
-
- ); - })} + ); + }, + )}
) : (
diff --git a/devfront/tests/devfront-clients-lifecycle.spec.ts b/devfront/tests/devfront-clients-lifecycle.spec.ts index a67bff76..a048eaa7 100644 --- a/devfront/tests/devfront-clients-lifecycle.spec.ts +++ b/devfront/tests/devfront-clients-lifecycle.spec.ts @@ -152,8 +152,7 @@ test.describe("DevFront clients lifecycle", () => { kty: "RSA", use: "sig", alg: "RS256", - n: - "voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ", + n: "voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ", }, ], }, @@ -162,11 +161,13 @@ test.describe("DevFront clients lifecycle", () => { consents: [] as Consent[], auditLogsByCursor: undefined, onRefreshHeadlessJwks(clientId: string) { - this.clients[0].headlessJwksCache = { - ...this.clients[0].headlessJwksCache!, - lastRefreshStatus: "success", - lastCheckedAt: "2026-04-01T00:00:00.000Z", - }; + if (this.clients[0].headlessJwksCache) { + this.clients[0].headlessJwksCache = { + ...this.clients[0].headlessJwksCache, + lastRefreshStatus: "success", + lastCheckedAt: "2026-04-01T00:00:00.000Z", + }; + } expect(clientId).toBe("client-headless-login"); }, onRevokeHeadlessJwksCache(clientId: string) { @@ -184,13 +185,17 @@ test.describe("DevFront clients lifecycle", () => { .click(); await expect( - page.getByRole("heading", { name: /공개키 등록|Public Key Registration/i }), + page.getByRole("heading", { + name: /공개키 등록|Public Key Registration/i, + }), ).toBeVisible(); await expect( page.getByText(/Request Object Signing Algorithm/i), ).toHaveCount(0); - await expect(page.getByText(/Allowed algorithms|허용 알고리즘/i)).toHaveCount(0); + await expect( + page.getByText(/Allowed algorithms|허용 알고리즘/i), + ).toHaveCount(0); await page .getByPlaceholder(/https:\/\/rp\.example\.com\/\.well-known\/jwks\.json/i) .fill(jwksUri); @@ -256,7 +261,9 @@ test.describe("DevFront clients lifecycle", () => { name: /공개키 등록|Public Key Registration/i, }), ).toBeVisible(); - await expect(page.getByRole("textbox", { name: /JWKS URI|JWKS URI/i })).toHaveValue(jwksUri); + await expect( + page.getByRole("textbox", { name: /JWKS URI|JWKS URI/i }), + ).toHaveValue(jwksUri); }); test("pkce headless login blocks save when parsed jwks algorithm is unsupported", async ({ @@ -306,9 +313,13 @@ test.describe("DevFront clients lifecycle", () => { .fill(jwksUri); await expect( - page.getByText("지원하지 않는 알고리즘이 감지되었습니다.", { exact: true }), + page.getByText("지원하지 않는 알고리즘이 감지되었습니다.", { + exact: true, + }), ).toBeVisible(); - await expect(page.getByRole("button", { name: /^저장$|^Save$/i })).toBeDisabled(); + await expect( + page.getByRole("button", { name: /^저장$|^Save$/i }), + ).toBeDisabled(); }); test("pkce headless login blocks save when parsed jwks algorithm is missing", async ({ @@ -356,6 +367,8 @@ test.describe("DevFront clients lifecycle", () => { await expect( page.getByText(/알고리즘이 선언되지 않았습니다|algorithm is missing/i), ).toBeVisible(); - await expect(page.getByRole("button", { name: /^저장$|^Save$/i })).toBeDisabled(); + await expect( + page.getByRole("button", { name: /^저장$|^Save$/i }), + ).toBeDisabled(); }); });