1
0
forked from baron/baron-sso

Trusted RP 설정 UX 및 안내 문구 개선

This commit is contained in:
2026-03-30 13:03:04 +09:00
parent 3ffc345c2c
commit 3a057ee860
7 changed files with 71 additions and 72 deletions

View File

@@ -48,7 +48,7 @@ interface ScopeItem {
mandatory: boolean;
}
type SecurityProfile = "private" | "pkce" | "private_key_jwt";
type SecurityProfile = "private" | "pkce";
function readMetadataString(
metadata: Record<string, unknown>,
@@ -101,10 +101,11 @@ function ClientGeneralPage() {
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState<
"none" | "client_secret_basic" | "private_key_jwt"
>("client_secret_basic");
const [jwksSource, setJwksSource] = useState<"uri" | "inline">("uri");
const [jwksSource, setJwksSource] = useState<"uri" | "inline">("inline");
const [jwksUri, setJwksUri] = useState("");
const [jwksText, setJwksText] = useState("");
const [requestObjectSigningAlg, setRequestObjectSigningAlg] = useState("RS256");
const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false);
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
{
@@ -150,6 +151,8 @@ function ClientGeneralPage() {
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");
@@ -188,38 +191,25 @@ function ClientGeneralPage() {
}
}, [data]);
const securityProfile: SecurityProfile =
tokenEndpointAuthMethod === "private_key_jwt"
? "private_key_jwt"
: clientType === "pkce"
? "pkce"
: "private";
const headlessLoginEnabled = securityProfile === "private_key_jwt";
const securityProfile: SecurityProfile = clientType === "pkce" ? "pkce" : "private";
const handleSecurityProfileChange = (profile: SecurityProfile) => {
setClientType(profile);
if (profile === "pkce") {
setClientType("pkce");
setTokenEndpointAuthMethod("none");
setJwksUri("");
setJwksText("");
setRequestObjectSigningAlg("");
return;
setTokenEndpointAuthMethod(headlessLoginEnabled ? "private_key_jwt" : "none");
} else {
setTokenEndpointAuthMethod("client_secret_basic");
}
};
setClientType("private");
if (profile === "private_key_jwt") {
setTokenEndpointAuthMethod("private_key_jwt");
if (requestObjectSigningAlg.trim() === "") {
const handleHeadlessToggle = (enabled: boolean) => {
setHeadlessLoginEnabled(enabled);
if (clientType === "pkce") {
setTokenEndpointAuthMethod(enabled ? "private_key_jwt" : "none");
if (enabled && requestObjectSigningAlg.trim() === "") {
setRequestObjectSigningAlg("RS256");
}
return;
}
setTokenEndpointAuthMethod("client_secret_basic");
setJwksUri("");
setJwksText("");
setRequestObjectSigningAlg("");
};
const addScope = () => {
@@ -794,7 +784,7 @@ function ClientGeneralPage() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<div className="grid gap-4 md:grid-cols-2">
<label
className={cn(
"relative flex cursor-pointer flex-col gap-1 rounded-xl border-2 p-4 transition",
@@ -856,43 +846,34 @@ function ClientGeneralPage() {
<span className="absolute right-4 top-4 text-primary">
{securityProfile === "pkce" ? "✓" : ""}
</span>
</label>
<label
className={cn(
"relative flex cursor-pointer flex-col gap-1 rounded-xl border-2 p-4 transition",
securityProfile === "private_key_jwt"
? "border-primary bg-primary/5"
: "border-border bg-card hover:border-muted-foreground/40",
{securityProfile === "pkce" && (
<div
className="mt-4 pt-4 border-t border-primary/20 flex items-center justify-between"
onClick={(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>
<p className="text-[10px] text-muted-foreground">
{t("ui.dev.clients.general.security.trusted_rp_enable_help", "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다.")}
</p>
</div>
<Switch
id="trusted-rp-toggle"
checked={headlessLoginEnabled}
onCheckedChange={handleHeadlessToggle}
/>
</div>
)}
>
<input
className="sr-only"
type="radio"
name="security-profile"
checked={securityProfile === "private_key_jwt"}
onChange={() => handleSecurityProfileChange("private_key_jwt")}
/>
<span className="flex items-center gap-2 text-sm font-bold uppercase text-foreground">
<Shield className="h-4 w-4 text-primary" />
{t("ui.dev.clients.general.security.trusted", "Trusted RP")}
</span>
<span className="whitespace-pre-line text-xs text-muted-foreground">
{t(
"msg.dev.clients.general.security.trusted_help",
"private_key_jwt와 공개키 등록을 사용해 trusted RP로 운영합니다. Headless Login은 이 프로필에서만 사용할 수 있습니다.",
)}
</span>
<span className="absolute right-4 top-4 text-primary">
{securityProfile === "private_key_jwt" ? "✓" : ""}
</span>
</label>
</div>
</CardContent>
</Card>
{/* 4. Public Key Registration (Trusted RP) */}
{securityProfile === "private_key_jwt" && (
{clientType === "pkce" && headlessLoginEnabled && (
<Card className="glass-panel border-primary/20">
<CardHeader className="pb-3">
<div className="flex flex-wrap items-start justify-between gap-3">
@@ -902,9 +883,6 @@ function ClientGeneralPage() {
"ui.dev.clients.general.public_key.title",
"Public Key Registration",
)}
<Badge variant="info" className="px-2 py-0.5 text-[10px] uppercase">
Trusted RP
</Badge>
</CardTitle>
<CardDescription>
{t(
@@ -928,7 +906,7 @@ function ClientGeneralPage() {
<p className="mt-1 text-xs text-muted-foreground">
{t(
"msg.dev.clients.general.public_key.headless_help",
"RP 자체 로그인 UI를 사용하고, 실제 인증 처리는 Baron Backend API를 통해 백그라운드에서 수행합니다.",
"애플리케이션 고유의 디자인으로 로그인 화면을 구성할 수 있습니다. 실제 아이디/비밀번호 확인 및 보안 검증 로직은 Baron API를 통해 백그라운드에서 처리됩니다.",
)}
</p>
</div>
@@ -985,21 +963,21 @@ function ClientGeneralPage() {
<input
type="radio"
name="jwksSource"
checked={jwksSource === "uri"}
onChange={() => setJwksSource("uri")}
checked={jwksSource === "inline"}
onChange={() => setJwksSource("inline")}
className="accent-primary"
/>
<span>JWKS URI ()</span>
<span>Inline Public Key</span>
</label>
<label className="flex items-center gap-2 text-sm">
<input
type="radio"
name="jwksSource"
checked={jwksSource === "inline"}
onChange={() => setJwksSource("inline")}
checked={jwksSource === "uri"}
onChange={() => setJwksSource("uri")}
className="accent-primary"
/>
<span>Inline Public Key ( )</span>
<span>JWKS URI</span>
</label>
</div>
@@ -1117,4 +1095,4 @@ function ClientGeneralPage() {
);
}
export default ClientGeneralPage;
export default ClientGeneralPage;