forked from baron/baron-sso
headless login SSA UX 재구성
This commit is contained in:
@@ -391,12 +391,13 @@ function ClientGeneralPage() {
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const { client } = data;
|
||||
const metadata = client.metadata ?? {};
|
||||
const headlessEnabled = !!metadata.headless_login_enabled;
|
||||
|
||||
setName(client.name || client.id);
|
||||
setClientType(client.type);
|
||||
setClientType(headlessEnabled ? "private" : client.type);
|
||||
setStatus(client.status);
|
||||
setInitialStatus(client.status);
|
||||
|
||||
const metadata = client.metadata ?? {};
|
||||
if (typeof metadata.description === "string")
|
||||
setDescription(metadata.description);
|
||||
if (typeof metadata.logo_url === "string") setLogoUrl(metadata.logo_url);
|
||||
@@ -412,7 +413,6 @@ function ClientGeneralPage() {
|
||||
if (typeof metadata.auto_login_url === "string")
|
||||
setAutoLoginUrl(metadata.auto_login_url);
|
||||
|
||||
const headlessEnabled = !!metadata.headless_login_enabled;
|
||||
setHeadlessLoginEnabled(headlessEnabled);
|
||||
const restrictedTenants = Array.isArray(metadata.allowed_tenants)
|
||||
? metadata.allowed_tenants
|
||||
@@ -532,18 +532,25 @@ function ClientGeneralPage() {
|
||||
const handleSecurityProfileChange = (profile: SecurityProfile) => {
|
||||
setClientType(profile);
|
||||
if (profile === "pkce") {
|
||||
setTokenEndpointAuthMethod(
|
||||
headlessLoginEnabled ? "private_key_jwt" : "none",
|
||||
);
|
||||
setHeadlessLoginEnabled(false);
|
||||
setTokenEndpointAuthMethod("none");
|
||||
} else {
|
||||
setTokenEndpointAuthMethod("client_secret_basic");
|
||||
setTokenEndpointAuthMethod(
|
||||
headlessLoginEnabled ? "private_key_jwt" : "client_secret_basic",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHeadlessToggle = (enabled: boolean) => {
|
||||
setHeadlessLoginEnabled(enabled);
|
||||
if (clientType === "pkce") {
|
||||
setTokenEndpointAuthMethod(enabled ? "private_key_jwt" : "none");
|
||||
if (enabled) {
|
||||
setClientType("private");
|
||||
setTokenEndpointAuthMethod("private_key_jwt");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientType === "private") {
|
||||
setTokenEndpointAuthMethod("client_secret_basic");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -974,14 +981,14 @@ function ClientGeneralPage() {
|
||||
.map((scope) => scope.name.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const effectiveTokenEndpointAuthMethod =
|
||||
clientType === "pkce" && headlessLoginEnabled
|
||||
? "none"
|
||||
: tokenEndpointAuthMethod;
|
||||
const persistedClientType = headlessLoginEnabled ? "pkce" : clientType;
|
||||
const effectiveTokenEndpointAuthMethod = headlessLoginEnabled
|
||||
? "none"
|
||||
: tokenEndpointAuthMethod;
|
||||
|
||||
const payload: ClientUpsertRequest = {
|
||||
name,
|
||||
type: clientType,
|
||||
type: persistedClientType,
|
||||
scopes: scopeNames,
|
||||
tokenEndpointAuthMethod: effectiveTokenEndpointAuthMethod,
|
||||
jwksUri:
|
||||
@@ -1003,14 +1010,10 @@ function ClientGeneralPage() {
|
||||
id_token_claims: normalizedIdTokenClaims,
|
||||
token_endpoint_auth_method: effectiveTokenEndpointAuthMethod,
|
||||
headless_login_enabled: headlessLoginEnabled,
|
||||
headless_token_endpoint_auth_method:
|
||||
clientType === "pkce" && headlessLoginEnabled
|
||||
? tokenEndpointAuthMethod
|
||||
: undefined,
|
||||
headless_jwks_uri:
|
||||
clientType === "pkce" && headlessLoginEnabled
|
||||
? trimmedJwksUri
|
||||
: undefined,
|
||||
headless_token_endpoint_auth_method: headlessLoginEnabled
|
||||
? tokenEndpointAuthMethod
|
||||
: undefined,
|
||||
headless_jwks_uri: headlessLoginEnabled ? trimmedJwksUri : undefined,
|
||||
tenant_access_restricted: tenantAccessRestricted,
|
||||
allowed_tenants: tenantAccessRestricted
|
||||
? normalizedAllowedTenantIds
|
||||
@@ -2291,6 +2294,38 @@ function ClientGeneralPage() {
|
||||
<span className="absolute right-4 top-4 text-primary">
|
||||
{securityProfile === "private" ? "✓" : ""}
|
||||
</span>
|
||||
|
||||
{securityProfile === "private" && (
|
||||
<div
|
||||
className="mt-4 flex items-center justify-between border-t border-primary/20 pt-4"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<Label
|
||||
className="cursor-pointer text-xs font-bold"
|
||||
htmlFor="headless-login-toggle"
|
||||
>
|
||||
{t(
|
||||
"ui.dev.clients.general.security.headless_login_enable",
|
||||
"Headless Login (자체 로그인 UI 사용)",
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.security.headless_login_enable_help",
|
||||
"Baron SSO 로그인 창 대신 RP 자체 로그인 UI를 사용하고, RP backend의 서명 키로 클라이언트를 검증하려는 경우 활성화합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="headless-login-toggle"
|
||||
checked={headlessLoginEnabled}
|
||||
onCheckedChange={handleHeadlessToggle}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -2321,45 +2356,13 @@ function ClientGeneralPage() {
|
||||
<span className="absolute right-4 top-4 text-primary">
|
||||
{securityProfile === "pkce" ? "✓" : ""}
|
||||
</span>
|
||||
|
||||
{securityProfile === "pkce" && (
|
||||
<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="headless-login-toggle"
|
||||
>
|
||||
{t(
|
||||
"ui.dev.clients.general.security.headless_login_enable",
|
||||
"Headless Login (자체 로그인 UI 사용)",
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{t(
|
||||
"ui.dev.clients.general.security.headless_login_enable_help",
|
||||
"Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="headless-login-toggle"
|
||||
checked={headlessLoginEnabled}
|
||||
onCheckedChange={handleHeadlessToggle}
|
||||
disabled={isGeneralSettingsReadOnly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 4. Public Key Registration (Headless Login) */}
|
||||
{clientType === "pkce" && headlessLoginEnabled && (
|
||||
{headlessLoginEnabled && (
|
||||
<Card className="glass-panel border-primary/20">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
@@ -2373,7 +2376,7 @@ function ClientGeneralPage() {
|
||||
<CardDescription>
|
||||
{t(
|
||||
"msg.dev.clients.general.public_key.subtitle",
|
||||
"Headless Login 판정에 필요한 공개키와 관련 설정을 관리합니다.",
|
||||
"Server side App의 Headless Login capability에 필요한 공개키와 검증 정보를 관리합니다.",
|
||||
)}
|
||||
</CardDescription>
|
||||
</div>
|
||||
@@ -2392,7 +2395,7 @@ function ClientGeneralPage() {
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.general.public_key.headless_help",
|
||||
"애플리케이션 고유의 디자인으로 로그인 화면을 구성할 수 있습니다. 실제 아이디/비밀번호 확인 및 보안 검증 로직은 Baron API를 통해 백그라운드에서 처리됩니다.",
|
||||
"RP가 자체 로그인 UI를 제공하더라도 실제 인증 흐름은 Baron API와 RP backend의 signed key 검증을 통해 이어집니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user