1
0
forked from baron/baron-sso

code-check 오류 수정

This commit is contained in:
2026-03-30 13:29:36 +09:00
parent cfe97ecb1e
commit c96a5350a7
10 changed files with 302 additions and 69 deletions

View File

@@ -49,6 +49,20 @@ interface ScopeItem {
}
type SecurityProfile = "private" | "pkce";
type TokenEndpointAuthMethod =
| "none"
| "client_secret_basic"
| "private_key_jwt";
function isTokenEndpointAuthMethod(
value: string,
): value is TokenEndpointAuthMethod {
return (
value === "none" ||
value === "client_secret_basic" ||
value === "private_key_jwt"
);
}
function readMetadataString(
metadata: Record<string, unknown>,
@@ -96,17 +110,17 @@ function ClientGeneralPage() {
const [status, setStatus] = useState<ClientStatus>("active");
const [initialStatus, setInitialStatus] = useState<ClientStatus>("active");
const [redirectUris, setRedirectUris] = useState("");
// Public Key Registration States
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState<
"none" | "client_secret_basic" | "private_key_jwt"
>("client_secret_basic");
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] =
useState<TokenEndpointAuthMethod>("client_secret_basic");
const [jwksSource, setJwksSource] = useState<"uri" | "inline">("inline");
const [jwksUri, setJwksUri] = useState("");
const [jwksText, setJwksText] = useState("");
const [requestObjectSigningAlg, setRequestObjectSigningAlg] = useState("RS256");
const [requestObjectSigningAlg, setRequestObjectSigningAlg] =
useState("RS256");
const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false);
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
{
id: "1",
@@ -136,40 +150,55 @@ function ClientGeneralPage() {
setStatus(client.status);
setInitialStatus(client.status);
const savedAuthMethod = client.tokenEndpointAuthMethod || (client.type === "pkce" ? "none" : "client_secret_basic");
setTokenEndpointAuthMethod(savedAuthMethod as any);
const savedAuthMethod =
client.tokenEndpointAuthMethod ||
(client.type === "pkce" ? "none" : "client_secret_basic");
if (isTokenEndpointAuthMethod(savedAuthMethod)) {
setTokenEndpointAuthMethod(savedAuthMethod);
}
if (client.jwksUri) {
setJwksUri(client.jwksUri);
setJwksSource("uri");
} else if (client.jwks) {
setJwksText(typeof client.jwks === 'string' ? client.jwks : JSON.stringify(client.jwks, null, 2));
setJwksText(
typeof client.jwks === "string"
? client.jwks
: JSON.stringify(client.jwks, null, 2),
);
setJwksSource("inline");
}
const metadata = client.metadata ?? {};
if (typeof metadata.description === "string") setDescription(metadata.description);
if (typeof metadata.description === "string")
setDescription(metadata.description);
if (typeof metadata.logo_url === "string") setLogoUrl(metadata.logo_url);
setHeadlessLoginEnabled(!!metadata.headless_login_enabled);
// Fallbacks from metadata if top-level fields are empty
if (!client.tokenEndpointAuthMethod) {
const metaAuth = readMetadataString(metadata, "token_endpoint_auth_method");
if (metaAuth === "none" || metaAuth === "client_secret_basic" || metaAuth === "private_key_jwt") {
setTokenEndpointAuthMethod(metaAuth);
}
const metaAuth = readMetadataString(
metadata,
"token_endpoint_auth_method",
);
if (isTokenEndpointAuthMethod(metaAuth)) {
setTokenEndpointAuthMethod(metaAuth);
}
}
if (!client.jwksUri && !client.jwks) {
const metaJwksUri = readMetadataString(metadata, "jwks_uri");
if (metaJwksUri) {
setJwksUri(metaJwksUri);
setJwksSource("uri");
}
const metaJwksUri = readMetadataString(metadata, "jwks_uri");
if (metaJwksUri) {
setJwksUri(metaJwksUri);
setJwksSource("uri");
}
}
const savedRequestObjectSigningAlg = readMetadataString(metadata, "request_object_signing_alg");
const savedRequestObjectSigningAlg = readMetadataString(
metadata,
"request_object_signing_alg",
);
if (savedRequestObjectSigningAlg) {
setRequestObjectSigningAlg(savedRequestObjectSigningAlg);
} else if (savedAuthMethod === "private_key_jwt") {
@@ -191,12 +220,15 @@ function ClientGeneralPage() {
}
}, [data]);
const securityProfile: SecurityProfile = clientType === "pkce" ? "pkce" : "private";
const securityProfile: SecurityProfile =
clientType === "pkce" ? "pkce" : "private";
const handleSecurityProfileChange = (profile: SecurityProfile) => {
setClientType(profile);
if (profile === "pkce") {
setTokenEndpointAuthMethod(headlessLoginEnabled ? "private_key_jwt" : "none");
setTokenEndpointAuthMethod(
headlessLoginEnabled ? "private_key_jwt" : "none",
);
} else {
setTokenEndpointAuthMethod("client_secret_basic");
}
@@ -261,15 +293,35 @@ function ClientGeneralPage() {
if (headlessLoginEnabled) {
if (jwksSource === "uri") {
if (!trimmedJwksUri) {
validationErrors.push(t("msg.dev.clients.general.public_key.validation.missing_jwks_uri", "JWKS URI를 입력해야 합니다."));
validationErrors.push(
t(
"msg.dev.clients.general.public_key.validation.missing_jwks_uri",
"JWKS URI를 입력해야 합니다.",
),
);
} else if (!isValidUrl(trimmedJwksUri)) {
validationErrors.push(t("msg.dev.clients.general.public_key.validation.invalid_jwks_uri", "JWKS URI 형식이 올바르지 않습니다."));
validationErrors.push(
t(
"msg.dev.clients.general.public_key.validation.invalid_jwks_uri",
"JWKS URI 형식이 올바르지 않습니다.",
),
);
}
} else if (jwksSource === "inline") {
if (!trimmedJwksText) {
validationErrors.push(t("msg.dev.clients.general.public_key.validation.missing_jwks_inline", "공개키(JWKS 또는 SSH-RSA)를 입력해야 합니다."));
validationErrors.push(
t(
"msg.dev.clients.general.public_key.validation.missing_jwks_inline",
"공개키(JWKS 또는 SSH-RSA)를 입력해야 합니다.",
),
);
} else if (!isValidJson(trimmedJwksText)) {
validationErrors.push(t("msg.dev.clients.general.public_key.validation.invalid_jwks_inline", "입력값이 유효한 JSON(JWKS) 형식이 아닙니다. SSH-RSA의 경우 'ssh-rsa'로 시작해야 합니다."));
validationErrors.push(
t(
"msg.dev.clients.general.public_key.validation.invalid_jwks_inline",
"입력값이 유효한 JSON(JWKS) 형식이 아닙니다. SSH-RSA의 경우 'ssh-rsa'로 시작해야 합니다.",
),
);
}
}
@@ -288,9 +340,13 @@ function ClientGeneralPage() {
const mutation = useMutation({
mutationFn: async () => {
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
let finalJwks: any = undefined;
if (tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "inline" && trimmedJwksText) {
let finalJwks: ClientUpsertRequest["jwks"];
if (
tokenEndpointAuthMethod === "private_key_jwt" &&
jwksSource === "inline" &&
trimmedJwksText
) {
try {
finalJwks = JSON.parse(trimmedJwksText);
} catch (e) {
@@ -303,7 +359,10 @@ function ClientGeneralPage() {
type: clientType,
scopes: scopeNames,
tokenEndpointAuthMethod,
jwksUri: tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "uri" ? trimmedJwksUri : undefined,
jwksUri:
tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "uri"
? trimmedJwksUri
: undefined,
jwks: finalJwks,
metadata: {
description,
@@ -848,22 +907,32 @@ function ClientGeneralPage() {
</span>
{securityProfile === "pkce" && (
<div
<div
className="mt-4 pt-4 border-t border-primary/20 flex items-center justify-between"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
<div className="space-y-0.5">
<Label className="text-xs font-bold cursor-pointer" htmlFor="trusted-rp-toggle">
{t("ui.dev.clients.general.security.trusted_rp_enable", "Trusted RP (자체 로그인 UI 사용)")}
<Label
className="text-xs font-bold cursor-pointer"
htmlFor="trusted-rp-toggle"
>
{t(
"ui.dev.clients.general.security.trusted_rp_enable",
"Trusted RP (자체 로그인 UI 사용)",
)}
</Label>
<p className="text-[10px] text-muted-foreground">
{t("ui.dev.clients.general.security.trusted_rp_enable_help", "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다.")}
{t(
"ui.dev.clients.general.security.trusted_rp_enable_help",
"Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다.",
)}
</p>
</div>
<Switch
<Switch
id="trusted-rp-toggle"
checked={headlessLoginEnabled}
onCheckedChange={handleHeadlessToggle}
checked={headlessLoginEnabled}
onCheckedChange={handleHeadlessToggle}
/>
</div>
)}
@@ -910,7 +979,10 @@ function ClientGeneralPage() {
)}
</p>
</div>
<Badge variant="default" className="bg-primary/20 text-primary border-primary/30">
<Badge
variant="default"
className="bg-primary/20 text-primary border-primary/30"
>
{t("ui.common.enabled", "Enabled")}
</Badge>
</div>
@@ -984,7 +1056,10 @@ function ClientGeneralPage() {
{jwksSource === "uri" && (
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
<Label className="text-sm font-semibold">
{t("ui.dev.clients.general.public_key.jwks_uri", "JWKS URI")}
{t(
"ui.dev.clients.general.public_key.jwks_uri",
"JWKS URI",
)}
<span className="text-destructive ml-1">*</span>
</Label>
<Input
@@ -1007,14 +1082,20 @@ function ClientGeneralPage() {
{jwksSource === "inline" && (
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
<Label className="text-sm font-semibold">
{t("ui.dev.clients.general.public_key.jwks_inline", "JWKS 또는 OpenSSH 공개키")}
{t(
"ui.dev.clients.general.public_key.jwks_inline",
"JWKS 또는 OpenSSH 공개키",
)}
<span className="text-destructive ml-1">*</span>
</Label>
<Textarea
rows={8}
value={jwksText}
onChange={(e) => setJwksText(e.target.value)}
placeholder={t("ui.dev.clients.general.public_key.jwks_inline_placeholder", "JWKS (JSON) 또는 'ssh-rsa AAA...' 형식의 공개키를 붙여넣으세요.")}
placeholder={t(
"ui.dev.clients.general.public_key.jwks_inline_placeholder",
"JWKS (JSON) 또는 'ssh-rsa AAA...' 형식의 공개키를 붙여넣으세요.",
)}
className="font-mono text-xs leading-tight"
/>
<p className="text-xs text-muted-foreground">