From 51f09bf53c2d8475ff672bbb728b614f0c708a6b Mon Sep 17 00:00:00 2001
From: Lectom C Han
Date: Wed, 1 Apr 2026 18:51:39 +0900
Subject: [PATCH] fix(headless-login): show full parsed jwks key values
- return the full RSA n value in parsedKeys responses
- render parsed key fields with labels and multiline key material in DevFront
- lock the behavior with backend and Playwright regression tests
---
.../internal/domain/headless_jwks_cache.go | 10 ++--
backend/internal/handler/dev_handler_test.go | 13 +----
.../internal/service/headless_jwks_cache.go | 18 ++-----
.../features/clients/ClientGeneralPage.tsx | 54 +++++++++++++------
devfront/src/lib/devApi.ts | 2 +-
.../tests/devfront-clients-lifecycle.spec.ts | 10 ++--
devfront/tests/helpers/devfront-fixtures.ts | 2 +-
7 files changed, 58 insertions(+), 51 deletions(-)
diff --git a/backend/internal/domain/headless_jwks_cache.go b/backend/internal/domain/headless_jwks_cache.go
index 2c48a5fa..082b69d6 100644
--- a/backend/internal/domain/headless_jwks_cache.go
+++ b/backend/internal/domain/headless_jwks_cache.go
@@ -3,11 +3,11 @@ package domain
import "time"
type HeadlessJWKSParsedKey struct {
- Kid string `json:"kid,omitempty"`
- Kty string `json:"kty,omitempty"`
- Use string `json:"use,omitempty"`
- Alg string `json:"alg,omitempty"`
- NPreview string `json:"nPreview,omitempty"`
+ Kid string `json:"kid,omitempty"`
+ Kty string `json:"kty,omitempty"`
+ Use string `json:"use,omitempty"`
+ Alg string `json:"alg,omitempty"`
+ N string `json:"n,omitempty"`
}
// HeadlessJWKSCacheState는 headless login용 JWKS 캐시 상태와 최근 동기화 결과를 나타냅니다.
diff --git a/backend/internal/handler/dev_handler_test.go b/backend/internal/handler/dev_handler_test.go
index cd6f2593..c694c35a 100644
--- a/backend/internal/handler/dev_handler_test.go
+++ b/backend/internal/handler/dev_handler_test.go
@@ -10,7 +10,6 @@ import (
"io"
"net/http"
"net/http/httptest"
- "strings"
"testing"
"time"
@@ -108,14 +107,6 @@ func devTestJWKSFirstKeyString(t *testing.T, jwks map[string]any, field string)
return value
}
-func devTestPreviewValue(value string) string {
- value = strings.TrimSpace(value)
- if len(value) <= 24 {
- return value
- }
- return value[:12] + "..." + value[len(value)-12:]
-}
-
// --- Tests ---
func TestListClients_Success(t *testing.T) {
@@ -855,7 +846,7 @@ func TestRefreshHeadlessJWKSCache_ReturnsUpdatedCacheState(t *testing.T) {
privateKey, jwks := mustHeadlessRSAJWK(t)
_ = privateKey
jwksBody, _ := json.Marshal(jwks)
- expectedNPreview := devTestPreviewValue(devTestJWKSFirstKeyString(t, jwks, "n"))
+ expectedN := devTestJWKSFirstKeyString(t, jwks, "n")
redisRepo := &devMockRedisRepo{data: map[string]string{}}
h := &DevHandler{
Hydra: &service.HydraAdminService{
@@ -908,7 +899,7 @@ func TestRefreshHeadlessJWKSCache_ReturnsUpdatedCacheState(t *testing.T) {
assert.Equal(t, "RSA", got.HeadlessJWKSCache.ParsedKeys[0].Kty)
assert.Equal(t, "sig", got.HeadlessJWKSCache.ParsedKeys[0].Use)
assert.Equal(t, "RS256", got.HeadlessJWKSCache.ParsedKeys[0].Alg)
- assert.Equal(t, expectedNPreview, got.HeadlessJWKSCache.ParsedKeys[0].NPreview)
+ assert.Equal(t, expectedN, got.HeadlessJWKSCache.ParsedKeys[0].N)
}
}
}
diff --git a/backend/internal/service/headless_jwks_cache.go b/backend/internal/service/headless_jwks_cache.go
index 29078ad4..2413a662 100644
--- a/backend/internal/service/headless_jwks_cache.go
+++ b/backend/internal/service/headless_jwks_cache.go
@@ -390,24 +390,16 @@ func summarizeHeadlessJWKS(raw string) []domain.HeadlessJWKSParsedKey {
parsedKeys := make([]domain.HeadlessJWKSParsedKey, 0, len(document.Keys))
for _, key := range document.Keys {
parsedKeys = append(parsedKeys, domain.HeadlessJWKSParsedKey{
- Kid: strings.TrimSpace(key.Kid),
- Kty: strings.TrimSpace(key.Kty),
- Use: strings.TrimSpace(key.Use),
- Alg: strings.TrimSpace(key.Alg),
- NPreview: previewHeadlessJWKValue(key.N),
+ Kid: strings.TrimSpace(key.Kid),
+ Kty: strings.TrimSpace(key.Kty),
+ Use: strings.TrimSpace(key.Use),
+ Alg: strings.TrimSpace(key.Alg),
+ N: strings.TrimSpace(key.N),
})
}
return parsedKeys
}
-func previewHeadlessJWKValue(value string) string {
- value = strings.TrimSpace(value)
- if len(value) <= 24 {
- return value
- }
- return value[:12] + "..." + value[len(value)-12:]
-}
-
func extractHeadlessKids(keySet *jose.JSONWebKeySet) []string {
if keySet == nil {
return nil
diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx
index a8db153c..76a93d35 100644
--- a/devfront/src/features/clients/ClientGeneralPage.tsx
+++ b/devfront/src/features/clients/ClientGeneralPage.tsx
@@ -1313,35 +1313,55 @@ function ClientGeneralPage() {
{currentHeadlessJwksCache.parsedKeys?.length ? (
-
+
{currentHeadlessJwksCache.parsedKeys.map((key, index) => (
-
-
- {key.kid || "-"}
-
-
- {key.kty || "-"}
-
-
- {key.use || "-"}
-
-
- {key.alg || "-"}
-
+
+
+
+ KID
+
+
+ {key.kid || "-"}
+
+
+
+
+ KTY
+
+
+ {key.kty || "-"}
+
+
+
+
+ USE
+
+
+ {key.use || "-"}
+
+
+
+
+ ALG
+
+
+ {key.alg || "-"}
+
+
{t(
"ui.dev.clients.general.public_key.cache.parsed_key_n",
- "n Preview",
+ "N",
)}
-
- {key.nPreview || "-"}
+
+ {key.n || "-"}
diff --git a/devfront/src/lib/devApi.ts b/devfront/src/lib/devApi.ts
index 6fd9faa1..db25ad10 100644
--- a/devfront/src/lib/devApi.ts
+++ b/devfront/src/lib/devApi.ts
@@ -80,7 +80,7 @@ export type ClientDetailResponse = {
kty?: string;
use?: string;
alg?: string;
- nPreview?: string;
+ n?: string;
}>;
};
};
diff --git a/devfront/tests/devfront-clients-lifecycle.spec.ts b/devfront/tests/devfront-clients-lifecycle.spec.ts
index cd811416..80a0d573 100644
--- a/devfront/tests/devfront-clients-lifecycle.spec.ts
+++ b/devfront/tests/devfront-clients-lifecycle.spec.ts
@@ -149,8 +149,8 @@ test.describe("DevFront clients lifecycle", () => {
kty: "RSA",
use: "sig",
alg: "RS256",
- nPreview:
- "voVbHlo_UHkj...Hb8PiTCQ",
+ n:
+ "voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ",
},
],
},
@@ -211,6 +211,7 @@ test.describe("DevFront clients lifecycle", () => {
page.getByText(/cached at|캐시됨|last refresh|마지막 갱신/i),
).toBeVisible();
await expect(page.getByText(/Parsed Keys|파싱된 키/i)).toBeVisible();
+ await expect(page.getByText(/^KID$/i)).toBeVisible();
await expect(page.getByText("kid-1", { exact: true }).last()).toBeVisible();
await expect(
page.getByText(/Allowed algorithms|허용 알고리즘/i),
@@ -230,7 +231,10 @@ test.describe("DevFront clients lifecycle", () => {
await expect(page.getByText(algorithm, { exact: true }).last()).toBeVisible();
}
await expect(
- page.getByText("voVbHlo_UHkj...Hb8PiTCQ", { exact: true }),
+ page.getByText(
+ "voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ",
+ { exact: true },
+ ),
).toBeVisible();
await expect(
page.getByRole("button", { name: /refresh|새로고침/i }),
diff --git a/devfront/tests/helpers/devfront-fixtures.ts b/devfront/tests/helpers/devfront-fixtures.ts
index 491d6629..d3643940 100644
--- a/devfront/tests/helpers/devfront-fixtures.ts
+++ b/devfront/tests/helpers/devfront-fixtures.ts
@@ -33,7 +33,7 @@ export type Client = {
kty?: string;
use?: string;
alg?: string;
- nPreview?: string;
+ n?: string;
}>;
};
metadata?: Record
;