forked from baron/baron-sso
chore(devfront): fix biome formatting and lints
This commit is contained in:
@@ -73,7 +73,10 @@ const HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET = new Set<string>(
|
|||||||
HEADLESS_LOGIN_ALLOWED_ALGORITHMS,
|
HEADLESS_LOGIN_ALLOWED_ALGORITHMS,
|
||||||
);
|
);
|
||||||
|
|
||||||
function formatHeadlessParsedKeyLabel(kid: string | undefined, index: number): string {
|
function formatHeadlessParsedKeyLabel(
|
||||||
|
kid: string | undefined,
|
||||||
|
index: number,
|
||||||
|
): string {
|
||||||
const trimmedKid = kid?.trim();
|
const trimmedKid = kid?.trim();
|
||||||
if (trimmedKid) {
|
if (trimmedKid) {
|
||||||
return trimmedKid;
|
return trimmedKid;
|
||||||
@@ -302,7 +305,7 @@ function ClientGeneralPage() {
|
|||||||
headlessLoginEnabled &&
|
headlessLoginEnabled &&
|
||||||
trimmedJwksUri !== "" &&
|
trimmedJwksUri !== "" &&
|
||||||
currentHeadlessJwksCache?.jwksUri === trimmedJwksUri
|
currentHeadlessJwksCache?.jwksUri === trimmedJwksUri
|
||||||
? currentHeadlessJwksCache.parsedKeys ?? []
|
? (currentHeadlessJwksCache.parsedKeys ?? [])
|
||||||
: [];
|
: [];
|
||||||
const unsupportedParsedAlgorithms = parsedKeysForCurrentJwksUri
|
const unsupportedParsedAlgorithms = parsedKeysForCurrentJwksUri
|
||||||
.map((key, index) => ({
|
.map((key, index) => ({
|
||||||
@@ -463,8 +466,7 @@ function ClientGeneralPage() {
|
|||||||
? tokenEndpointAuthMethod
|
? tokenEndpointAuthMethod
|
||||||
: undefined,
|
: undefined,
|
||||||
headless_jwks_uri:
|
headless_jwks_uri:
|
||||||
clientType === "pkce" &&
|
clientType === "pkce" && headlessLoginEnabled
|
||||||
headlessLoginEnabled
|
|
||||||
? trimmedJwksUri
|
? trimmedJwksUri
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
@@ -1148,9 +1150,7 @@ function ClientGeneralPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() =>
|
onClick={() => refreshHeadlessJwksCacheMutation.mutate()}
|
||||||
refreshHeadlessJwksCacheMutation.mutate()
|
|
||||||
}
|
|
||||||
disabled={refreshHeadlessJwksCacheMutation.isPending}
|
disabled={refreshHeadlessJwksCacheMutation.isPending}
|
||||||
>
|
>
|
||||||
{refreshHeadlessJwksCacheMutation.isPending
|
{refreshHeadlessJwksCacheMutation.isPending
|
||||||
@@ -1202,10 +1202,7 @@ function ClientGeneralPage() {
|
|||||||
"Status",
|
"Status",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<Badge
|
<Badge variant="info" className="w-fit capitalize">
|
||||||
variant="info"
|
|
||||||
className="w-fit capitalize"
|
|
||||||
>
|
|
||||||
{currentHeadlessJwksCache.lastRefreshStatus ||
|
{currentHeadlessJwksCache.lastRefreshStatus ||
|
||||||
t("ui.common.unknown", "Unknown")}
|
t("ui.common.unknown", "Unknown")}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -1237,7 +1234,9 @@ function ClientGeneralPage() {
|
|||||||
"Expires At",
|
"Expires At",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>{formatDateTime(currentHeadlessJwksCache.expiresAt)}</p>
|
<p>
|
||||||
|
{formatDateTime(currentHeadlessJwksCache.expiresAt)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||||
@@ -1247,9 +1246,7 @@ function ClientGeneralPage() {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{formatDateTime(
|
{formatDateTime(currentHeadlessJwksCache.lastCheckedAt)}
|
||||||
currentHeadlessJwksCache.lastCheckedAt,
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -1272,9 +1269,7 @@ function ClientGeneralPage() {
|
|||||||
"Consecutive Failures",
|
"Consecutive Failures",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>{currentHeadlessJwksCache.consecutiveFailures ?? 0}</p>
|
||||||
{currentHeadlessJwksCache.consecutiveFailures ?? 0}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||||
@@ -1346,101 +1341,104 @@ function ClientGeneralPage() {
|
|||||||
</div>
|
</div>
|
||||||
{currentHeadlessJwksCache.parsedKeys?.length ? (
|
{currentHeadlessJwksCache.parsedKeys?.length ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentHeadlessJwksCache.parsedKeys.map((key, index) => {
|
{currentHeadlessJwksCache.parsedKeys.map(
|
||||||
const normalizedAlgorithm = key.alg?.trim() ?? "";
|
(key, index) => {
|
||||||
const isMissingAlgorithm =
|
const normalizedAlgorithm = key.alg?.trim() ?? "";
|
||||||
normalizedAlgorithm === "";
|
const isMissingAlgorithm =
|
||||||
const isUnsupportedAlgorithm =
|
normalizedAlgorithm === "";
|
||||||
!isMissingAlgorithm &&
|
const isUnsupportedAlgorithm =
|
||||||
!HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has(
|
!isMissingAlgorithm &&
|
||||||
normalizedAlgorithm,
|
!HEADLESS_LOGIN_ALLOWED_ALGORITHM_SET.has(
|
||||||
);
|
normalizedAlgorithm,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${key.kid || "key"}-${index}`}
|
key={`${key.kid || "key"}-${index}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl border bg-muted/30 p-3",
|
"rounded-xl border bg-muted/30 p-3",
|
||||||
isUnsupportedAlgorithm || isMissingAlgorithm
|
isUnsupportedAlgorithm || isMissingAlgorithm
|
||||||
? "border-destructive/50 bg-destructive/5"
|
? "border-destructive/50 bg-destructive/5"
|
||||||
: "border-border",
|
: "border-border",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
KID
|
KID
|
||||||
</p>
|
</p>
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
{key.kid || "-"}
|
{key.kid || "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
KTY
|
KTY
|
||||||
</p>
|
</p>
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
{key.kty || "-"}
|
{key.kty || "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
USE
|
USE
|
||||||
</p>
|
</p>
|
||||||
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
<p className="break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px]">
|
||||||
{key.use || "-"}
|
{key.use || "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
ALG
|
ALG
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"break-all rounded-lg border bg-background px-3 py-2 font-mono text-[11px]",
|
"break-all rounded-lg border bg-background px-3 py-2 font-mono text-[11px]",
|
||||||
isUnsupportedAlgorithm || isMissingAlgorithm
|
isUnsupportedAlgorithm ||
|
||||||
? "border-destructive/50 text-destructive"
|
isMissingAlgorithm
|
||||||
: "border-border",
|
? "border-destructive/50 text-destructive"
|
||||||
|
: "border-border",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{key.alg ||
|
||||||
|
t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.missing_algorithm_badge",
|
||||||
|
"알고리즘 미선언",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{isMissingAlgorithm && (
|
||||||
|
<p className="text-[11px] text-destructive">
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.missing_algorithm_reason",
|
||||||
|
"이 키는 `alg`가 비어 있어서 저장할 수 없습니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{isUnsupportedAlgorithm && (
|
||||||
|
<p className="text-[11px] text-destructive">
|
||||||
|
{t(
|
||||||
|
"msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason",
|
||||||
|
"이 알고리즘은 Headless Login에서 지원되지 않습니다.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 space-y-1">
|
||||||
|
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
||||||
|
{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",
|
|
||||||
"알고리즘 미선언",
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
{isMissingAlgorithm && (
|
<p className="min-h-16 break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px] leading-5">
|
||||||
<p className="text-[11px] text-destructive">
|
{key.n || "-"}
|
||||||
{t(
|
</p>
|
||||||
"msg.dev.clients.general.public_key.cache.missing_algorithm_reason",
|
|
||||||
"이 키는 `alg`가 비어 있어서 저장할 수 없습니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{isUnsupportedAlgorithm && (
|
|
||||||
<p className="text-[11px] text-destructive">
|
|
||||||
{t(
|
|
||||||
"msg.dev.clients.general.public_key.cache.unsupported_algorithm_reason",
|
|
||||||
"이 알고리즘은 Headless Login에서 지원되지 않습니다.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 space-y-1">
|
);
|
||||||
<p className="text-[11px] font-semibold uppercase text-muted-foreground">
|
},
|
||||||
{t(
|
)}
|
||||||
"ui.dev.clients.general.public_key.cache.parsed_key_n",
|
|
||||||
"N",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="min-h-16 break-all rounded-lg border border-border bg-background px-3 py-2 font-mono text-[11px] leading-5">
|
|
||||||
{key.n || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-lg border border-dashed border-border px-4 py-5 text-sm text-muted-foreground">
|
<div className="rounded-lg border border-dashed border-border px-4 py-5 text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -152,8 +152,7 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
kty: "RSA",
|
kty: "RSA",
|
||||||
use: "sig",
|
use: "sig",
|
||||||
alg: "RS256",
|
alg: "RS256",
|
||||||
n:
|
n: "voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ",
|
||||||
"voVbHlo_UHkjtT7Q_8owyjZ2omE8n8mbGlpraZziStHPfe08q_RGiEXO6Pyiz42NVi-Yo0c7qiaqRwB4h9s5phpT2wwcUxnkrQeRhe7BpigInZPzpwq1hsaB2zyhE7zTRCC3hinGtFdVpNzTVKYKGPbXfeEXaRL3P838vi-_iB4IN3WQk_pAakUQvajL2H-vcWSMSNslMGPDZxobqE9MHSWocNXemrcmtCeE7ruUND0qHZOb8k-hHUBqsNoJ63WKdapzGYF6e2qgDRveYrjgOCBigZPi8npN0xStQ0YcrH_RxeTogsdRZ8SuXmLqavryVDnrT8czPkkJ-EHb8PiTCQ",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -162,11 +161,13 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
consents: [] as Consent[],
|
consents: [] as Consent[],
|
||||||
auditLogsByCursor: undefined,
|
auditLogsByCursor: undefined,
|
||||||
onRefreshHeadlessJwks(clientId: string) {
|
onRefreshHeadlessJwks(clientId: string) {
|
||||||
this.clients[0].headlessJwksCache = {
|
if (this.clients[0].headlessJwksCache) {
|
||||||
...this.clients[0].headlessJwksCache!,
|
this.clients[0].headlessJwksCache = {
|
||||||
lastRefreshStatus: "success",
|
...this.clients[0].headlessJwksCache,
|
||||||
lastCheckedAt: "2026-04-01T00:00:00.000Z",
|
lastRefreshStatus: "success",
|
||||||
};
|
lastCheckedAt: "2026-04-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
}
|
||||||
expect(clientId).toBe("client-headless-login");
|
expect(clientId).toBe("client-headless-login");
|
||||||
},
|
},
|
||||||
onRevokeHeadlessJwksCache(clientId: string) {
|
onRevokeHeadlessJwksCache(clientId: string) {
|
||||||
@@ -184,13 +185,17 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("heading", { name: /공개키 등록|Public Key Registration/i }),
|
page.getByRole("heading", {
|
||||||
|
name: /공개키 등록|Public Key Registration/i,
|
||||||
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(/Request Object Signing Algorithm/i),
|
page.getByText(/Request Object Signing Algorithm/i),
|
||||||
).toHaveCount(0);
|
).toHaveCount(0);
|
||||||
await expect(page.getByText(/Allowed algorithms|허용 알고리즘/i)).toHaveCount(0);
|
await expect(
|
||||||
|
page.getByText(/Allowed algorithms|허용 알고리즘/i),
|
||||||
|
).toHaveCount(0);
|
||||||
await page
|
await page
|
||||||
.getByPlaceholder(/https:\/\/rp\.example\.com\/\.well-known\/jwks\.json/i)
|
.getByPlaceholder(/https:\/\/rp\.example\.com\/\.well-known\/jwks\.json/i)
|
||||||
.fill(jwksUri);
|
.fill(jwksUri);
|
||||||
@@ -256,7 +261,9 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
name: /공개키 등록|Public Key Registration/i,
|
name: /공개키 등록|Public Key Registration/i,
|
||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).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 ({
|
test("pkce headless login blocks save when parsed jwks algorithm is unsupported", async ({
|
||||||
@@ -306,9 +313,13 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
.fill(jwksUri);
|
.fill(jwksUri);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("지원하지 않는 알고리즘이 감지되었습니다.", { exact: true }),
|
page.getByText("지원하지 않는 알고리즘이 감지되었습니다.", {
|
||||||
|
exact: true,
|
||||||
|
}),
|
||||||
).toBeVisible();
|
).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 ({
|
test("pkce headless login blocks save when parsed jwks algorithm is missing", async ({
|
||||||
@@ -356,6 +367,8 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByText(/알고리즘이 선언되지 않았습니다|algorithm is missing/i),
|
page.getByText(/알고리즘이 선언되지 않았습니다|algorithm is missing/i),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(page.getByRole("button", { name: /^저장$|^Save$/i })).toBeDisabled();
|
await expect(
|
||||||
|
page.getByRole("button", { name: /^저장$|^Save$/i }),
|
||||||
|
).toBeDisabled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user