forked from baron/baron-sso
클레임 토글 및 비활성화 저장 처리 추가
This commit is contained in:
@@ -334,6 +334,47 @@ describe("ClientGeneralPage RP claims", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("clears saved RP claims when custom claims are disabled", async () => {
|
||||
const { container } = await renderPage();
|
||||
|
||||
const claimToggle = Array.from(
|
||||
container.querySelectorAll<HTMLButtonElement>('[role="switch"]'),
|
||||
).find((button) =>
|
||||
(button.getAttribute("aria-label") ?? "").includes("커스텀 클레임 사용"),
|
||||
);
|
||||
expect(claimToggle).toBeDefined();
|
||||
expect(claimToggle?.getAttribute("aria-checked")).toBe("true");
|
||||
|
||||
await act(async () => {
|
||||
claimToggle?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(claimToggle?.getAttribute("aria-checked")).toBe("false");
|
||||
|
||||
const saveButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) =>
|
||||
button.textContent?.includes("저장") ||
|
||||
button.textContent?.includes("Save"),
|
||||
);
|
||||
expect(saveButton).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(updateClientMock).toHaveBeenCalledWith(
|
||||
"client-claims",
|
||||
expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
id_token_claims_enabled: false,
|
||||
id_token_claims: [],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves tenants scope mandatory state when tenant access restriction is off", async () => {
|
||||
fetchClientMock.mockResolvedValue(
|
||||
makeClientDetail("old_claim", {
|
||||
|
||||
@@ -633,6 +633,7 @@ function ClientGeneralPage() {
|
||||
const [allowedTenantIds, setAllowedTenantIds] = useState<string[]>([]);
|
||||
const [autoLoginSupported, setAutoLoginSupported] = useState(false);
|
||||
const [autoLoginUrl, setAutoLoginUrl] = useState("");
|
||||
const [idTokenClaimsEnabled, setIdTokenClaimsEnabled] = useState(false);
|
||||
|
||||
// Public Key Registration States
|
||||
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] =
|
||||
@@ -956,7 +957,13 @@ function ClientGeneralPage() {
|
||||
),
|
||||
);
|
||||
}
|
||||
setIdTokenClaims(readIdTokenClaimsMetadata(metadata));
|
||||
const savedIdTokenClaims = readIdTokenClaimsMetadata(metadata);
|
||||
setIdTokenClaims(savedIdTokenClaims);
|
||||
setIdTokenClaimsEnabled(
|
||||
typeof metadata.id_token_claims_enabled === "boolean"
|
||||
? metadata.id_token_claims_enabled
|
||||
: savedIdTokenClaims.length > 0,
|
||||
);
|
||||
}, [data, normalizeScopesForTenantAccess]);
|
||||
|
||||
const securityProfile: SecurityProfile =
|
||||
@@ -1089,6 +1096,7 @@ function ClientGeneralPage() {
|
||||
};
|
||||
|
||||
const addIdTokenClaim = () => {
|
||||
setIdTokenClaimsEnabled(true);
|
||||
setIdTokenClaims((current) => [
|
||||
...current,
|
||||
createIdTokenClaimItem(`claim-${Date.now()}`),
|
||||
@@ -1253,6 +1261,7 @@ function ClientGeneralPage() {
|
||||
}
|
||||
|
||||
const claimValidationErrors: string[] = [];
|
||||
if (idTokenClaimsEnabled) {
|
||||
const seenClaimKeys = new Set<string>();
|
||||
for (const claim of normalizedIdTokenClaimItems) {
|
||||
if (!claim.key) {
|
||||
@@ -1289,12 +1298,13 @@ function ClientGeneralPage() {
|
||||
claimValidationErrors.push(defaultValueError);
|
||||
}
|
||||
}
|
||||
}
|
||||
validationErrors.push(...claimValidationErrors);
|
||||
|
||||
const hasValidationErrors = validationErrors.length > 0;
|
||||
const idTokenClaimPreview = buildIdTokenClaimsPreview(
|
||||
normalizedIdTokenClaimItems,
|
||||
);
|
||||
const idTokenClaimPreview = idTokenClaimsEnabled
|
||||
? buildIdTokenClaimsPreview(normalizedIdTokenClaimItems)
|
||||
: [];
|
||||
const idTokenClaimPreviewJson = JSON.stringify(idTokenClaimPreview, null, 2);
|
||||
const tenantOptions: TenantSummary[] = tenantData?.items ?? [];
|
||||
const selectedAllowedTenants = allowedTenantIds
|
||||
@@ -1435,7 +1445,8 @@ function ClientGeneralPage() {
|
||||
auto_login_supported: autoLoginSupported,
|
||||
auto_login_url: autoLoginSupported ? trimmedAutoLoginUrl : undefined,
|
||||
structured_scopes: normalizedScopes,
|
||||
id_token_claims: normalizedIdTokenClaims,
|
||||
id_token_claims_enabled: idTokenClaimsEnabled,
|
||||
id_token_claims: idTokenClaimsEnabled ? normalizedIdTokenClaims : [],
|
||||
token_endpoint_auth_method: effectiveTokenEndpointAuthMethod,
|
||||
headless_login_enabled: headlessLoginEnabled,
|
||||
headless_token_endpoint_auth_method: headlessLoginEnabled
|
||||
@@ -1908,7 +1919,7 @@ function ClientGeneralPage() {
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={addScope}
|
||||
className="gap-2"
|
||||
@@ -2315,6 +2326,7 @@ function ClientGeneralPage() {
|
||||
<Card className="glass-panel">
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<CardTitle className="text-xl font-bold">
|
||||
{t(
|
||||
@@ -2329,16 +2341,49 @@ function ClientGeneralPage() {
|
||||
)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
{idTokenClaimsEnabled ? (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={addIdTokenClaim}
|
||||
className="gap-2"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
{t("ui.dev.clients.general.id_token_claims.add", "Claim 추가")}
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.add",
|
||||
"Claim 추가",
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-3 rounded-xl border border-border bg-muted/30 px-4 py-3">
|
||||
<div className="space-y-0.5 text-right">
|
||||
<p className="text-sm font-semibold">
|
||||
{idTokenClaimsEnabled
|
||||
? t("ui.common.enabled", "사용")
|
||||
: t("ui.common.disabled", "사용 안 함")}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.enabled",
|
||||
"커스텀 클레임 사용",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={idTokenClaimsEnabled}
|
||||
onCheckedChange={setIdTokenClaimsEnabled}
|
||||
id="custom-claims-enabled"
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.enabled",
|
||||
"커스텀 클레임 사용",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{idTokenClaimsEnabled ? (
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid gap-4 xl:grid-cols-[minmax(0,1.45fr)_minmax(360px,0.75fr)]">
|
||||
<div className="space-y-3">
|
||||
@@ -2652,7 +2697,10 @@ function ClientGeneralPage() {
|
||||
)}
|
||||
>
|
||||
{timeZoneOptions.map((timeZone) => (
|
||||
<option key={timeZone} value={timeZone}>
|
||||
<option
|
||||
key={timeZone}
|
||||
value={timeZone}
|
||||
>
|
||||
{timeZone}
|
||||
</option>
|
||||
))}
|
||||
@@ -2727,6 +2775,7 @@ function ClientGeneralPage() {
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
{/* 4. Tenant Access Restriction */}
|
||||
@@ -2994,6 +3043,7 @@ function ClientGeneralPage() {
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{autoLoginSupported ? (
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="auto-login-url" className="text-sm font-semibold">
|
||||
@@ -3006,7 +3056,6 @@ function ClientGeneralPage() {
|
||||
id="auto-login-url"
|
||||
value={autoLoginUrl}
|
||||
onChange={(event) => setAutoLoginUrl(event.target.value)}
|
||||
disabled={!autoLoginSupported}
|
||||
aria-invalid={!hasValidAutoLoginUrl}
|
||||
className={!hasValidAutoLoginUrl ? "border-destructive" : ""}
|
||||
placeholder={t(
|
||||
@@ -3030,6 +3079,7 @@ function ClientGeneralPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
{/* 6. Security Settings */}
|
||||
|
||||
@@ -1649,6 +1649,7 @@ picker_hint_with_count = "{{count}} tenants selected."
|
||||
[ui.dev.clients.general.id_token_claims]
|
||||
title = "Custom Claims"
|
||||
add = "Add Claim"
|
||||
enabled = "Custom Claims Enabled"
|
||||
preview_title = "Saved JSON Preview"
|
||||
namespace_label = "Claim namespace"
|
||||
namespace_top_level = "top-level"
|
||||
|
||||
@@ -1648,6 +1648,7 @@ picker_hint_with_count = "현재 {{count}}개가 선택되어 있습니다."
|
||||
[ui.dev.clients.general.id_token_claims]
|
||||
title = "커스텀 클레임"
|
||||
add = "Claim 추가"
|
||||
enabled = "커스텀 클레임 사용"
|
||||
preview_title = "저장 JSON 미리보기"
|
||||
namespace_label = "Claim 네임스페이스"
|
||||
namespace_top_level = "top-level"
|
||||
|
||||
@@ -1698,6 +1698,7 @@ picker_hint_with_count = ""
|
||||
[ui.dev.clients.general.id_token_claims]
|
||||
title = ""
|
||||
add = ""
|
||||
enabled = ""
|
||||
preview_title = ""
|
||||
namespace_label = ""
|
||||
namespace_top_level = ""
|
||||
|
||||
Reference in New Issue
Block a user