forked from baron/baron-sso
custom claim 타입보정 UI. 대표테넌트 노출 보정
This commit is contained in:
@@ -71,6 +71,7 @@ type ClaimNamespace = "rp_claims";
|
||||
type ClaimValueType =
|
||||
| "text"
|
||||
| "number"
|
||||
| "float"
|
||||
| "boolean"
|
||||
| "array"
|
||||
| "object"
|
||||
@@ -149,6 +150,7 @@ function isClaimValueType(value: string): value is ClaimValueType {
|
||||
return (
|
||||
value === "text" ||
|
||||
value === "number" ||
|
||||
value === "float" ||
|
||||
value === "boolean" ||
|
||||
value === "array" ||
|
||||
value === "object" ||
|
||||
@@ -176,6 +178,18 @@ function createIdTokenClaimItem(id: string): IdTokenClaimItem {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeIdTokenClaimPermissions(
|
||||
claim: IdTokenClaimItem,
|
||||
): IdTokenClaimItem {
|
||||
if (claim.writePermission !== "user_and_admin") {
|
||||
return claim;
|
||||
}
|
||||
return {
|
||||
...claim,
|
||||
readPermission: "user_and_admin",
|
||||
};
|
||||
}
|
||||
|
||||
function readIdTokenClaimsMetadata(
|
||||
metadata: Record<string, unknown>,
|
||||
): IdTokenClaimItem[] {
|
||||
@@ -213,7 +227,7 @@ function readIdTokenClaimsMetadata(
|
||||
? record.valueType
|
||||
: "text";
|
||||
|
||||
return {
|
||||
return normalizeIdTokenClaimPermissions({
|
||||
id: `claim-${index + 1}`,
|
||||
namespace: namespaceValue,
|
||||
key: keyValue,
|
||||
@@ -226,7 +240,7 @@ function readIdTokenClaimsMetadata(
|
||||
writePermission: isCustomClaimPermission(record.writePermission)
|
||||
? record.writePermission
|
||||
: "admin_only",
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter((item): item is IdTokenClaimItem => item !== null);
|
||||
}
|
||||
@@ -240,7 +254,7 @@ function normalizeClaimPreviewValue(
|
||||
if (nullable && trimmed === "") {
|
||||
return null;
|
||||
}
|
||||
if (valueType === "number") {
|
||||
if (valueType === "number" || valueType === "float") {
|
||||
if (trimmed === "") return "";
|
||||
const parsed = Number(trimmed);
|
||||
return Number.isFinite(parsed) ? parsed : trimmed;
|
||||
@@ -279,6 +293,137 @@ function normalizeClaimPreviewValue(
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function isJsonObjectValue(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isIntegerClaimDefaultValue(value: string) {
|
||||
return /^-?\d+$/.test(value);
|
||||
}
|
||||
|
||||
function isFloatClaimDefaultValue(value: string) {
|
||||
return /^-?(?:\d+(?:\.\d+)?|\.\d+)$/.test(value);
|
||||
}
|
||||
|
||||
function isValidDateInputValue(value: string) {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
||||
const date = new Date(`${value}T00:00:00Z`);
|
||||
if (Number.isNaN(date.getTime())) return false;
|
||||
return date.toISOString().slice(0, 10) === value;
|
||||
}
|
||||
|
||||
function isValidDateTimeInputValue(value: string) {
|
||||
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
const date = new Date(value);
|
||||
return !Number.isNaN(date.getTime());
|
||||
}
|
||||
|
||||
function claimDefaultValueValidationError(claim: IdTokenClaimItem) {
|
||||
const value = claim.value.trim();
|
||||
if (value === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (claim.valueType) {
|
||||
case "number":
|
||||
return isIntegerClaimDefaultValue(value)
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
case "float":
|
||||
return isFloatClaimDefaultValue(value)
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
case "boolean":
|
||||
return value === "true" || value === "false"
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
case "array": {
|
||||
try {
|
||||
return Array.isArray(JSON.parse(value))
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
} catch {
|
||||
return t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
}
|
||||
}
|
||||
case "object": {
|
||||
try {
|
||||
return isJsonObjectValue(JSON.parse(value))
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
} catch {
|
||||
return t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
}
|
||||
}
|
||||
case "date":
|
||||
return isValidDateInputValue(value)
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
case "datetime":
|
||||
return isValidDateTimeInputValue(value)
|
||||
? null
|
||||
: t(
|
||||
"msg.dev.clients.general.id_token_claims.invalid_default_value",
|
||||
"Claim 기본값이 타입과 맞지 않습니다: {{key}} ({{valueType}})",
|
||||
{ key: claim.key || "-", valueType: claim.valueType },
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function claimDefaultInputType(valueType: ClaimValueType) {
|
||||
if (valueType === "date") return "date";
|
||||
if (valueType === "datetime") return "datetime-local";
|
||||
return "text";
|
||||
}
|
||||
|
||||
function claimDefaultInputMode(valueType: ClaimValueType) {
|
||||
if (valueType === "number") return "numeric";
|
||||
if (valueType === "float") return "decimal";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function claimDefaultInputPattern(valueType: ClaimValueType) {
|
||||
if (valueType === "number") return "-?[0-9]*";
|
||||
if (valueType === "float") return "-?(?:[0-9]+(?:\\.[0-9]+)?|\\.[0-9]+)";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildIdTokenClaimsPreview(
|
||||
items: IdTokenClaimItem[],
|
||||
): Record<string, unknown> {
|
||||
@@ -777,10 +922,10 @@ function ClientGeneralPage() {
|
||||
if (claim.id !== id) {
|
||||
return claim;
|
||||
}
|
||||
return {
|
||||
return normalizeIdTokenClaimPermissions({
|
||||
...claim,
|
||||
[field]: permission,
|
||||
};
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -840,11 +985,13 @@ function ClientGeneralPage() {
|
||||
"허용 알고리즘: {{algorithms}}",
|
||||
{ algorithms: HEADLESS_LOGIN_ALLOWED_ALGORITHMS.join(", ") },
|
||||
);
|
||||
const normalizedIdTokenClaims = idTokenClaims.map((claim) => ({
|
||||
...claim,
|
||||
key: claim.key.trim(),
|
||||
value: claim.value.trim(),
|
||||
}));
|
||||
const normalizedIdTokenClaims = idTokenClaims.map((claim) =>
|
||||
normalizeIdTokenClaimPermissions({
|
||||
...claim,
|
||||
key: claim.key.trim(),
|
||||
value: claim.value.trim(),
|
||||
}),
|
||||
);
|
||||
|
||||
if (headlessLoginEnabled) {
|
||||
if (!trimmedJwksUri) {
|
||||
@@ -930,6 +1077,11 @@ function ClientGeneralPage() {
|
||||
continue;
|
||||
}
|
||||
seenClaimKeys.add(keySignature);
|
||||
|
||||
const defaultValueError = claimDefaultValueValidationError(claim);
|
||||
if (defaultValueError) {
|
||||
claimValidationErrors.push(defaultValueError);
|
||||
}
|
||||
}
|
||||
validationErrors.push(...claimValidationErrors);
|
||||
|
||||
@@ -2103,7 +2255,7 @@ function ClientGeneralPage() {
|
||||
<CardDescription>
|
||||
{t(
|
||||
"msg.dev.clients.general.id_token_claims.subtitle",
|
||||
"공통 claim과 RP 전용 확장 claim을 구분해서 관리합니다.",
|
||||
"RP 전용 확장 claim을 구분해서 관리합니다.",
|
||||
)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
@@ -2151,13 +2303,13 @@ function ClientGeneralPage() {
|
||||
<th className="px-4 py-3 text-left font-bold">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.table.read_user_allowed",
|
||||
"Read",
|
||||
"User read",
|
||||
)}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-bold">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.table.write_user_allowed",
|
||||
"Write",
|
||||
"User write",
|
||||
)}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-bold">
|
||||
@@ -2175,190 +2327,255 @@ function ClientGeneralPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{idTokenClaims.map((claim) => (
|
||||
<tr key={claim.id} className="hover:bg-muted/20">
|
||||
<td className="px-4 py-3 align-top">
|
||||
<Input
|
||||
value={claim.key}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"key",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="h-9 font-mono text-xs"
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.id_token_claims.key_placeholder",
|
||||
"e.g. locale",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<Badge
|
||||
variant="muted"
|
||||
className="h-9 rounded-md border bg-muted/40 px-3 py-2 font-mono text-xs"
|
||||
>
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.namespace_rp_claims",
|
||||
"rp_claims",
|
||||
)}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<select
|
||||
value={claim.valueType}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"valueType",
|
||||
e.target.value as ClaimValueType,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_label",
|
||||
"Claim value type",
|
||||
)}
|
||||
className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
>
|
||||
<option value="text">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_text",
|
||||
"Text",
|
||||
)}
|
||||
</option>
|
||||
<option value="number">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_number",
|
||||
"Number",
|
||||
)}
|
||||
</option>
|
||||
<option value="boolean">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_boolean",
|
||||
"Boolean",
|
||||
)}
|
||||
</option>
|
||||
<option value="array">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_array",
|
||||
"Array",
|
||||
)}
|
||||
</option>
|
||||
<option value="object">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_object",
|
||||
"Object",
|
||||
)}
|
||||
</option>
|
||||
<option value="date">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_date",
|
||||
"Date",
|
||||
)}
|
||||
</option>
|
||||
<option value="datetime">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_datetime",
|
||||
"Datetime",
|
||||
)}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={claim.nullable}
|
||||
onCheckedChange={(checked) =>
|
||||
{idTokenClaims.map((claim) => {
|
||||
const defaultValueError =
|
||||
claimDefaultValueValidationError(claim);
|
||||
|
||||
return (
|
||||
<tr key={claim.id} className="hover:bg-muted/20">
|
||||
<td className="px-4 py-3 align-top">
|
||||
<Input
|
||||
value={claim.key}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"nullable",
|
||||
checked,
|
||||
"key",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.nullable_label",
|
||||
"Nullable",
|
||||
className="h-9 font-mono text-xs"
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.id_token_claims.key_placeholder",
|
||||
"e.g. locale",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={
|
||||
claim.readPermission === "user_and_admin"
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
setIdTokenClaimPermissionAllowed(
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<Badge
|
||||
variant="muted"
|
||||
className="h-9 rounded-md border bg-muted/40 px-3 py-2 font-mono text-xs"
|
||||
>
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.namespace_rp_claims",
|
||||
"rp_claims",
|
||||
)}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<select
|
||||
value={claim.valueType}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"readPermission",
|
||||
checked,
|
||||
"valueType",
|
||||
e.target.value as ClaimValueType,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.read_user_allowed_label",
|
||||
"Read 사용자 허용",
|
||||
"ui.dev.clients.general.id_token_claims.value_type_label",
|
||||
"Claim 값 타입",
|
||||
)}
|
||||
className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={
|
||||
claim.writePermission === "user_and_admin"
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
setIdTokenClaimPermissionAllowed(
|
||||
claim.id,
|
||||
"writePermission",
|
||||
checked,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.write_user_allowed_label",
|
||||
"Write 사용자 허용",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<Input
|
||||
value={claim.value}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"value",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="h-9 font-mono text-xs"
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.id_token_claims.value_placeholder",
|
||||
"Enter the default value",
|
||||
>
|
||||
<option value="text">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_text",
|
||||
"Text",
|
||||
)}
|
||||
</option>
|
||||
<option value="number">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_number",
|
||||
"Number",
|
||||
)}
|
||||
</option>
|
||||
<option value="float">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_float",
|
||||
"Float",
|
||||
)}
|
||||
</option>
|
||||
<option value="boolean">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_boolean",
|
||||
"Boolean",
|
||||
)}
|
||||
</option>
|
||||
<option value="array">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_array",
|
||||
"Array",
|
||||
)}
|
||||
</option>
|
||||
<option value="object">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_object",
|
||||
"Object",
|
||||
)}
|
||||
</option>
|
||||
<option value="date">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_date",
|
||||
"Date",
|
||||
)}
|
||||
</option>
|
||||
<option value="datetime">
|
||||
{t(
|
||||
"ui.dev.clients.general.id_token_claims.value_type_datetime",
|
||||
"Datetime",
|
||||
)}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={claim.nullable}
|
||||
onCheckedChange={(checked) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"nullable",
|
||||
checked,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.nullable_label",
|
||||
"Nullable",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={
|
||||
claim.readPermission === "user_and_admin"
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
setIdTokenClaimPermissionAllowed(
|
||||
claim.id,
|
||||
"readPermission",
|
||||
checked,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.read_user_allowed_label",
|
||||
"사용자 읽기 허용",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
<div className="flex h-9 items-center">
|
||||
<Switch
|
||||
checked={
|
||||
claim.writePermission === "user_and_admin"
|
||||
}
|
||||
onCheckedChange={(checked) =>
|
||||
setIdTokenClaimPermissionAllowed(
|
||||
claim.id,
|
||||
"writePermission",
|
||||
checked,
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.id_token_claims.write_user_allowed_label",
|
||||
"사용자 쓰기 허용",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 align-top">
|
||||
{claim.valueType === "array" ||
|
||||
claim.valueType === "object" ? (
|
||||
<Textarea
|
||||
value={claim.value}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"value",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="min-h-9 font-mono text-xs"
|
||||
placeholder={
|
||||
claim.valueType === "array"
|
||||
? `["value"]`
|
||||
: `{"key": "value"}`
|
||||
}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
) : claim.valueType === "boolean" ? (
|
||||
<select
|
||||
value={
|
||||
claim.value === "false" ? "false" : "true"
|
||||
}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"value",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="h-9 w-full rounded-md border border-input bg-background px-3 font-mono text-xs shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
>
|
||||
<option value="true">true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
) : (
|
||||
<Input
|
||||
type={claimDefaultInputType(claim.valueType)}
|
||||
inputMode={claimDefaultInputMode(
|
||||
claim.valueType,
|
||||
)}
|
||||
pattern={claimDefaultInputPattern(
|
||||
claim.valueType,
|
||||
)}
|
||||
value={claim.value}
|
||||
onChange={(e) =>
|
||||
updateIdTokenClaim(
|
||||
claim.id,
|
||||
"value",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="h-9 font-mono text-xs"
|
||||
placeholder={t(
|
||||
"ui.dev.clients.general.id_token_claims.value_placeholder",
|
||||
"Enter the default value",
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
aria-invalid={
|
||||
defaultValueError ? true : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right align-top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeIdTokenClaim(claim.id)}
|
||||
className="h-9 w-9 text-muted-foreground hover:text-destructive"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{defaultValueError && (
|
||||
<p className="mt-1 text-xs text-destructive">
|
||||
{defaultValueError}
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right align-top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeIdTokenClaim(claim.id)}
|
||||
className="h-9 w-9 text-muted-foreground hover:text-destructive"
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{idTokenClaims.length === 0 && (
|
||||
<tr>
|
||||
<td
|
||||
@@ -2378,7 +2595,7 @@ function ClientGeneralPage() {
|
||||
<p className="text-xs leading-6 text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.id_token_claims.hint",
|
||||
"RP 전용 확장 claim만 관리합니다. 배열은 JSON 또는 콤마 구분 문자열, 객체는 JSON을 입력하면 됩니다.",
|
||||
"RP 전용 확장 claim을 구분해서 관리합니다. 사용자별 claim 값은 동의 및 Claims 탭에서 수정합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user