forked from baron/baron-sso
code-check 오류 수정
This commit is contained in:
@@ -143,7 +143,7 @@ type clientUpsertRequest struct {
|
|||||||
ResponseTypes *[]string `json:"responseTypes"`
|
ResponseTypes *[]string `json:"responseTypes"`
|
||||||
TokenEndpointAuthMethod *string `json:"tokenEndpointAuthMethod"`
|
TokenEndpointAuthMethod *string `json:"tokenEndpointAuthMethod"`
|
||||||
JwksUri *string `json:"jwksUri"`
|
JwksUri *string `json:"jwksUri"`
|
||||||
Jwks interface{} `json:"jwks"`
|
Jwks interface{} `json:"jwks"`
|
||||||
Metadata *map[string]interface{} `json:"metadata"`
|
Metadata *map[string]interface{} `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,20 @@ interface ScopeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SecurityProfile = "private" | "pkce";
|
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(
|
function readMetadataString(
|
||||||
metadata: Record<string, unknown>,
|
metadata: Record<string, unknown>,
|
||||||
@@ -96,17 +110,17 @@ function ClientGeneralPage() {
|
|||||||
const [status, setStatus] = useState<ClientStatus>("active");
|
const [status, setStatus] = useState<ClientStatus>("active");
|
||||||
const [initialStatus, setInitialStatus] = useState<ClientStatus>("active");
|
const [initialStatus, setInitialStatus] = useState<ClientStatus>("active");
|
||||||
const [redirectUris, setRedirectUris] = useState("");
|
const [redirectUris, setRedirectUris] = useState("");
|
||||||
|
|
||||||
// Public Key Registration States
|
// Public Key Registration States
|
||||||
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState<
|
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] =
|
||||||
"none" | "client_secret_basic" | "private_key_jwt"
|
useState<TokenEndpointAuthMethod>("client_secret_basic");
|
||||||
>("client_secret_basic");
|
|
||||||
const [jwksSource, setJwksSource] = useState<"uri" | "inline">("inline");
|
const [jwksSource, setJwksSource] = useState<"uri" | "inline">("inline");
|
||||||
const [jwksUri, setJwksUri] = useState("");
|
const [jwksUri, setJwksUri] = useState("");
|
||||||
const [jwksText, setJwksText] = useState("");
|
const [jwksText, setJwksText] = useState("");
|
||||||
const [requestObjectSigningAlg, setRequestObjectSigningAlg] = useState("RS256");
|
const [requestObjectSigningAlg, setRequestObjectSigningAlg] =
|
||||||
|
useState("RS256");
|
||||||
const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false);
|
const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false);
|
||||||
|
|
||||||
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
|
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
@@ -136,40 +150,55 @@ function ClientGeneralPage() {
|
|||||||
setStatus(client.status);
|
setStatus(client.status);
|
||||||
setInitialStatus(client.status);
|
setInitialStatus(client.status);
|
||||||
|
|
||||||
const savedAuthMethod = client.tokenEndpointAuthMethod || (client.type === "pkce" ? "none" : "client_secret_basic");
|
const savedAuthMethod =
|
||||||
setTokenEndpointAuthMethod(savedAuthMethod as any);
|
client.tokenEndpointAuthMethod ||
|
||||||
|
(client.type === "pkce" ? "none" : "client_secret_basic");
|
||||||
|
if (isTokenEndpointAuthMethod(savedAuthMethod)) {
|
||||||
|
setTokenEndpointAuthMethod(savedAuthMethod);
|
||||||
|
}
|
||||||
|
|
||||||
if (client.jwksUri) {
|
if (client.jwksUri) {
|
||||||
setJwksUri(client.jwksUri);
|
setJwksUri(client.jwksUri);
|
||||||
setJwksSource("uri");
|
setJwksSource("uri");
|
||||||
} else if (client.jwks) {
|
} 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");
|
setJwksSource("inline");
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = client.metadata ?? {};
|
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);
|
if (typeof metadata.logo_url === "string") setLogoUrl(metadata.logo_url);
|
||||||
|
|
||||||
setHeadlessLoginEnabled(!!metadata.headless_login_enabled);
|
setHeadlessLoginEnabled(!!metadata.headless_login_enabled);
|
||||||
|
|
||||||
// Fallbacks from metadata if top-level fields are empty
|
// Fallbacks from metadata if top-level fields are empty
|
||||||
if (!client.tokenEndpointAuthMethod) {
|
if (!client.tokenEndpointAuthMethod) {
|
||||||
const metaAuth = readMetadataString(metadata, "token_endpoint_auth_method");
|
const metaAuth = readMetadataString(
|
||||||
if (metaAuth === "none" || metaAuth === "client_secret_basic" || metaAuth === "private_key_jwt") {
|
metadata,
|
||||||
setTokenEndpointAuthMethod(metaAuth);
|
"token_endpoint_auth_method",
|
||||||
}
|
);
|
||||||
|
if (isTokenEndpointAuthMethod(metaAuth)) {
|
||||||
|
setTokenEndpointAuthMethod(metaAuth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.jwksUri && !client.jwks) {
|
if (!client.jwksUri && !client.jwks) {
|
||||||
const metaJwksUri = readMetadataString(metadata, "jwks_uri");
|
const metaJwksUri = readMetadataString(metadata, "jwks_uri");
|
||||||
if (metaJwksUri) {
|
if (metaJwksUri) {
|
||||||
setJwksUri(metaJwksUri);
|
setJwksUri(metaJwksUri);
|
||||||
setJwksSource("uri");
|
setJwksSource("uri");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedRequestObjectSigningAlg = readMetadataString(metadata, "request_object_signing_alg");
|
const savedRequestObjectSigningAlg = readMetadataString(
|
||||||
|
metadata,
|
||||||
|
"request_object_signing_alg",
|
||||||
|
);
|
||||||
if (savedRequestObjectSigningAlg) {
|
if (savedRequestObjectSigningAlg) {
|
||||||
setRequestObjectSigningAlg(savedRequestObjectSigningAlg);
|
setRequestObjectSigningAlg(savedRequestObjectSigningAlg);
|
||||||
} else if (savedAuthMethod === "private_key_jwt") {
|
} else if (savedAuthMethod === "private_key_jwt") {
|
||||||
@@ -191,12 +220,15 @@ function ClientGeneralPage() {
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const securityProfile: SecurityProfile = clientType === "pkce" ? "pkce" : "private";
|
const securityProfile: SecurityProfile =
|
||||||
|
clientType === "pkce" ? "pkce" : "private";
|
||||||
|
|
||||||
const handleSecurityProfileChange = (profile: SecurityProfile) => {
|
const handleSecurityProfileChange = (profile: SecurityProfile) => {
|
||||||
setClientType(profile);
|
setClientType(profile);
|
||||||
if (profile === "pkce") {
|
if (profile === "pkce") {
|
||||||
setTokenEndpointAuthMethod(headlessLoginEnabled ? "private_key_jwt" : "none");
|
setTokenEndpointAuthMethod(
|
||||||
|
headlessLoginEnabled ? "private_key_jwt" : "none",
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setTokenEndpointAuthMethod("client_secret_basic");
|
setTokenEndpointAuthMethod("client_secret_basic");
|
||||||
}
|
}
|
||||||
@@ -261,15 +293,35 @@ function ClientGeneralPage() {
|
|||||||
if (headlessLoginEnabled) {
|
if (headlessLoginEnabled) {
|
||||||
if (jwksSource === "uri") {
|
if (jwksSource === "uri") {
|
||||||
if (!trimmedJwksUri) {
|
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)) {
|
} 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") {
|
} else if (jwksSource === "inline") {
|
||||||
if (!trimmedJwksText) {
|
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)) {
|
} 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({
|
const mutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
|
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
|
||||||
|
|
||||||
let finalJwks: any = undefined;
|
let finalJwks: ClientUpsertRequest["jwks"];
|
||||||
if (tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "inline" && trimmedJwksText) {
|
if (
|
||||||
|
tokenEndpointAuthMethod === "private_key_jwt" &&
|
||||||
|
jwksSource === "inline" &&
|
||||||
|
trimmedJwksText
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
finalJwks = JSON.parse(trimmedJwksText);
|
finalJwks = JSON.parse(trimmedJwksText);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -303,7 +359,10 @@ function ClientGeneralPage() {
|
|||||||
type: clientType,
|
type: clientType,
|
||||||
scopes: scopeNames,
|
scopes: scopeNames,
|
||||||
tokenEndpointAuthMethod,
|
tokenEndpointAuthMethod,
|
||||||
jwksUri: tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "uri" ? trimmedJwksUri : undefined,
|
jwksUri:
|
||||||
|
tokenEndpointAuthMethod === "private_key_jwt" && jwksSource === "uri"
|
||||||
|
? trimmedJwksUri
|
||||||
|
: undefined,
|
||||||
jwks: finalJwks,
|
jwks: finalJwks,
|
||||||
metadata: {
|
metadata: {
|
||||||
description,
|
description,
|
||||||
@@ -848,22 +907,32 @@ function ClientGeneralPage() {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{securityProfile === "pkce" && (
|
{securityProfile === "pkce" && (
|
||||||
<div
|
<div
|
||||||
className="mt-4 pt-4 border-t border-primary/20 flex items-center justify-between"
|
className="mt-4 pt-4 border-t border-primary/20 flex items-center justify-between"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label className="text-xs font-bold cursor-pointer" htmlFor="trusted-rp-toggle">
|
<Label
|
||||||
{t("ui.dev.clients.general.security.trusted_rp_enable", "Trusted RP (자체 로그인 UI 사용)")}
|
className="text-xs font-bold cursor-pointer"
|
||||||
|
htmlFor="trusted-rp-toggle"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"ui.dev.clients.general.security.trusted_rp_enable",
|
||||||
|
"Trusted RP (자체 로그인 UI 사용)",
|
||||||
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
id="trusted-rp-toggle"
|
id="trusted-rp-toggle"
|
||||||
checked={headlessLoginEnabled}
|
checked={headlessLoginEnabled}
|
||||||
onCheckedChange={handleHeadlessToggle}
|
onCheckedChange={handleHeadlessToggle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -910,7 +979,10 @@ function ClientGeneralPage() {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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")}
|
{t("ui.common.enabled", "Enabled")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -984,7 +1056,10 @@ function ClientGeneralPage() {
|
|||||||
{jwksSource === "uri" && (
|
{jwksSource === "uri" && (
|
||||||
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
|
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
|
||||||
<Label className="text-sm font-semibold">
|
<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>
|
<span className="text-destructive ml-1">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -1007,14 +1082,20 @@ function ClientGeneralPage() {
|
|||||||
{jwksSource === "inline" && (
|
{jwksSource === "inline" && (
|
||||||
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
|
<div className="space-y-2 animate-in fade-in slide-in-from-top-2">
|
||||||
<Label className="text-sm font-semibold">
|
<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>
|
<span className="text-destructive ml-1">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
rows={8}
|
rows={8}
|
||||||
value={jwksText}
|
value={jwksText}
|
||||||
onChange={(e) => setJwksText(e.target.value)}
|
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"
|
className="font-mono text-xs leading-tight"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function toBase64Url(base64: string): string {
|
|||||||
function hexToBase64Url(hex: string): string {
|
function hexToBase64Url(hex: string): string {
|
||||||
const binary = hex
|
const binary = hex
|
||||||
.match(/.{1,2}/g)
|
.match(/.{1,2}/g)
|
||||||
?.map((byte) => String.fromCharCode(parseInt(byte, 16)))
|
?.map((byte) => String.fromCharCode(Number.parseInt(byte, 16)))
|
||||||
.join("");
|
.join("");
|
||||||
if (!binary) return "";
|
if (!binary) return "";
|
||||||
return toBase64Url(btoa(binary));
|
return toBase64Url(btoa(binary));
|
||||||
@@ -42,12 +42,12 @@ export function parsePemToJwk(pem: string): JWK | null {
|
|||||||
.replace(/-----END PUBLIC KEY-----/, "")
|
.replace(/-----END PUBLIC KEY-----/, "")
|
||||||
.replace(/\s/g, "");
|
.replace(/\s/g, "");
|
||||||
|
|
||||||
// In a real browser environment without heavy libraries,
|
// In a real browser environment without heavy libraries,
|
||||||
// we would need a full ASN.1 parser.
|
// we would need a full ASN.1 parser.
|
||||||
// For now, we recommend using JWKS or OpenSSH formats for reliability,
|
// For now, we recommend using JWKS or OpenSSH formats for reliability,
|
||||||
// or we can hint the user that complex PEMs might fail.
|
// or we can hint the user that complex PEMs might fail.
|
||||||
// However, we'll try to support a basic one.
|
// However, we'll try to support a basic one.
|
||||||
|
|
||||||
return null; // Placeholder: PEM parsing is complex without libs.
|
return null; // Placeholder: PEM parsing is complex without libs.
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse PEM", e);
|
console.error("Failed to parse PEM", e);
|
||||||
@@ -100,12 +100,12 @@ export function parseSshRsaToJwk(sshKey: string): JWK | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function semanticsBase64Url(blob: string): string {
|
function semanticsBase64Url(blob: string): string {
|
||||||
// Ensure leading zero removal for BigInt representations if necessary
|
// Ensure leading zero removal for BigInt representations if necessary
|
||||||
let start = 0;
|
let start = 0;
|
||||||
while (start < blob.length && blob.charCodeAt(start) === 0) {
|
while (start < blob.length && blob.charCodeAt(start) === 0) {
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
return toBase64Url(btoa(blob.slice(start)));
|
return toBase64Url(btoa(blob.slice(start)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +114,7 @@ function semanticsBase64Url(blob: string): string {
|
|||||||
*/
|
*/
|
||||||
export function tryConvertToJwks(input: string): string {
|
export function tryConvertToJwks(input: string): string {
|
||||||
const trimmed = input.trim();
|
const trimmed = input.trim();
|
||||||
|
|
||||||
// 1. If it looks like JSON, return as is (validation happens in component)
|
// 1. If it looks like JSON, return as is (validation happens in component)
|
||||||
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
||||||
return trimmed;
|
return trimmed;
|
||||||
@@ -130,9 +130,9 @@ export function tryConvertToJwks(input: string): string {
|
|||||||
|
|
||||||
// 3. PEM (Simplified check)
|
// 3. PEM (Simplified check)
|
||||||
if (trimmed.includes("BEGIN PUBLIC KEY")) {
|
if (trimmed.includes("BEGIN PUBLIC KEY")) {
|
||||||
// For PEM, we suggest the user uses JWKS or SSH-RSA for now
|
// For PEM, we suggest the user uses JWKS or SSH-RSA for now
|
||||||
// as JS doesn't have a built-in ASN1 parser and we want to avoid heavy deps.
|
// as JS doesn't have a built-in ASN1 parser and we want to avoid heavy deps.
|
||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmed;
|
return trimmed;
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const state = {
|
const state = {
|
||||||
clients: [makeClient("client-trusted", { name: "Trusted App", type: "pkce" })],
|
clients: [
|
||||||
|
makeClient("client-trusted", { name: "Trusted App", type: "pkce" }),
|
||||||
|
],
|
||||||
consents: [] as Consent[],
|
consents: [] as Consent[],
|
||||||
auditLogsByCursor: undefined,
|
auditLogsByCursor: undefined,
|
||||||
};
|
};
|
||||||
@@ -154,24 +156,30 @@ test.describe("DevFront clients lifecycle", () => {
|
|||||||
.fill(sshRsaPublicKey);
|
.fill(sshRsaPublicKey);
|
||||||
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
|
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
|
||||||
|
|
||||||
await expect.poll(() => state.clients[0]?.tokenEndpointAuthMethod).toBe(
|
await expect
|
||||||
"private_key_jwt",
|
.poll(() => state.clients[0]?.tokenEndpointAuthMethod)
|
||||||
);
|
.toBe("private_key_jwt");
|
||||||
await expect
|
await expect
|
||||||
.poll(() => state.clients[0]?.metadata?.headless_login_enabled)
|
.poll(() => state.clients[0]?.metadata?.headless_login_enabled)
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
() =>
|
() =>
|
||||||
(state.clients[0]?.jwks as { keys?: Array<{ kty?: string; alg?: string }> })
|
(
|
||||||
?.keys?.[0]?.kty,
|
state.clients[0]?.jwks as {
|
||||||
|
keys?: Array<{ kty?: string; alg?: string }>;
|
||||||
|
}
|
||||||
|
)?.keys?.[0]?.kty,
|
||||||
)
|
)
|
||||||
.toBe("RSA");
|
.toBe("RSA");
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
() =>
|
() =>
|
||||||
(state.clients[0]?.jwks as { keys?: Array<{ kty?: string; alg?: string }> })
|
(
|
||||||
?.keys?.[0]?.alg,
|
state.clients[0]?.jwks as {
|
||||||
|
keys?: Array<{ kty?: string; alg?: string }>;
|
||||||
|
}
|
||||||
|
)?.keys?.[0]?.alg,
|
||||||
)
|
)
|
||||||
.toBe("RS256");
|
.toBe("RS256");
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,32 @@ limit_notice = "Showing members from the first 10 descendant organizations due t
|
|||||||
[msg.admin.tenants.registry]
|
[msg.admin.tenants.registry]
|
||||||
count = "{{count}} tenants loaded."
|
count = "{{count}} tenants loaded."
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key]
|
||||||
|
auth_method_client_secret_basic_help = "Standard authentication method for server-side applications."
|
||||||
|
auth_method_none_help = "Use this for PKCE-based public clients."
|
||||||
|
auth_method_private_key_jwt_help = "Signed key-based client authentication recommended for trusted RP bootstrap and JAR verification."
|
||||||
|
guide_example = "Recommended example: https://rp.example.com/.well-known/jwks.json"
|
||||||
|
guide_intro = "A JWKS URI is not created by Baron. It is the URL where the RP backend exposes its public key."
|
||||||
|
guide_step_1 = "Generate a key pair on the RP server and keep the private key only in the RP backend."
|
||||||
|
guide_step_2 = "Expose the public key from the RP backend through a JWKS (JSON Web Key Set) endpoint."
|
||||||
|
guide_step_3 = "Enter a URL such as https://rp.example.com/.well-known/jwks.json in DevFront."
|
||||||
|
headless_help = "You can design your own login UI within the application. While the UI is yours, the actual identity verification and security checks are handled in the background via Baron's API."
|
||||||
|
jwks_inline_help = "Prefer the SSH-RSA public key format first. If you paste an 'ssh-rsa AAA...' key, Baron converts it to OIDC-standard JWKS (JSON) before saving."
|
||||||
|
jwks_uri_help = "Enter the public key endpoint URL exposed by the RP backend. Example: https://rp.example.com/.well-known/jwks.json"
|
||||||
|
request_object_alg_help = "Specify the JAR (Request Object) signing algorithm used for headless login."
|
||||||
|
source_help = "Register the JWKS URI served by the RP so Baron can verify the public key."
|
||||||
|
subtitle = "Manage the public key and headless login settings required for trusted RP evaluation."
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key.validation]
|
||||||
|
headless_requires_alg = "Headless login requires a Request Object Signing Algorithm."
|
||||||
|
headless_requires_private_key_jwt = "Headless login requires token endpoint auth method to be private_key_jwt."
|
||||||
|
headless_requires_public_key = "Headless login requires a JWKS URI."
|
||||||
|
invalid_jwks_inline = "The input must be valid JSON (JWKS). For SSH-RSA input, it must start with 'ssh-rsa'."
|
||||||
|
invalid_jwks_uri = "JWKS URI format is invalid."
|
||||||
|
missing_jwks_inline = "Enter a public key in SSH-RSA or JWKS format."
|
||||||
|
missing_jwks_uri = "JWKS URI is required."
|
||||||
|
private_key_jwt_requires_public_key = "Signed key-based authentication requires a JWKS URI."
|
||||||
|
|
||||||
[msg.admin.tenants.schema]
|
[msg.admin.tenants.schema]
|
||||||
empty = "No custom fields defined. Click \"Add Field\" to begin."
|
empty = "No custom fields defined. Click \"Add Field\" to begin."
|
||||||
missing_id = "Tenant ID missing"
|
missing_id = "Tenant ID missing"
|
||||||
@@ -1215,6 +1241,7 @@ create = "Create"
|
|||||||
delete = "Delete"
|
delete = "Delete"
|
||||||
details = "Details"
|
details = "Details"
|
||||||
edit = "Edit"
|
edit = "Edit"
|
||||||
|
enabled = "Enabled"
|
||||||
export = "Export"
|
export = "Export"
|
||||||
fail = "Fail"
|
fail = "Fail"
|
||||||
go_home = "Go Home"
|
go_home = "Go Home"
|
||||||
@@ -1462,6 +1489,26 @@ title = "Security Settings"
|
|||||||
trusted_rp_enable = "Trusted RP (Custom Login UI)"
|
trusted_rp_enable = "Trusted RP (Custom Login UI)"
|
||||||
trusted_rp_enable_help = "Enable this if you want to implement your own login screen within the app instead of using the Baron SSO login page."
|
trusted_rp_enable_help = "Enable this if you want to implement your own login screen within the app instead of using the Baron SSO login page."
|
||||||
|
|
||||||
|
[ui.dev.clients.general.public_key]
|
||||||
|
auth_method = "Token Endpoint Auth Method"
|
||||||
|
auth_method_client_secret_basic = "client_secret_basic"
|
||||||
|
auth_method_none = "none"
|
||||||
|
auth_method_private_key_jwt = "Signed Key Authentication"
|
||||||
|
guide_toggle = "JWKS URI Setup Guide"
|
||||||
|
headless_disabled = "Headless Disabled"
|
||||||
|
headless_enabled = "Headless Enabled"
|
||||||
|
headless_toggle = "Headless Login"
|
||||||
|
jwks_inline = "SSH-RSA or JWKS Public Key"
|
||||||
|
jwks_inline_placeholder = "Paste an 'ssh-rsa AAA...' public key first. JWKS (JSON) is also accepted if needed."
|
||||||
|
jwks_uri = "JWKS URI"
|
||||||
|
jwks_uri_placeholder = "https://rp.example.com/.well-known/jwks.json"
|
||||||
|
request_object_alg = "Request Object Signing Algorithm"
|
||||||
|
request_object_alg_placeholder = "RS256"
|
||||||
|
source = "Public Key Source"
|
||||||
|
source_uri = "JWKS URI"
|
||||||
|
title = "Public Key Registration"
|
||||||
|
validation_title = "Check before saving"
|
||||||
|
|
||||||
[ui.dev.clients.help]
|
[ui.dev.clients.help]
|
||||||
docs_body = "Includes PKCE, client_secret_basic, redirect URI validation tips."
|
docs_body = "Includes PKCE, client_secret_basic, redirect URI validation tips."
|
||||||
docs_title = "Docs & Examples"
|
docs_title = "Docs & Examples"
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ create = "생성"
|
|||||||
delete = "삭제"
|
delete = "삭제"
|
||||||
details = "상세정보"
|
details = "상세정보"
|
||||||
edit = "편집"
|
edit = "편집"
|
||||||
|
enabled = "사용"
|
||||||
export = "내보내기"
|
export = "내보내기"
|
||||||
fail = "실패"
|
fail = "실패"
|
||||||
go_home = "홈으로"
|
go_home = "홈으로"
|
||||||
@@ -229,6 +230,32 @@ showing = "전체 {{total}}개 중 {{shown}}개를 표시하는 중입니다."
|
|||||||
notice = "개발자 전용 콘솔입니다."
|
notice = "개발자 전용 콘솔입니다."
|
||||||
notice_detail = "연동 앱 등록 및 관리를 수행할 수 있습니다."
|
notice_detail = "연동 앱 등록 및 관리를 수행할 수 있습니다."
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key]
|
||||||
|
auth_method_client_secret_basic_help = "일반적인 서버 사이드 앱 인증 방식입니다."
|
||||||
|
auth_method_none_help = "PKCE 기반 public client에 사용하는 방식입니다."
|
||||||
|
auth_method_private_key_jwt_help = "Trusted RP bootstrap과 JAR 검증에 필요한 서명 키 기반 인증 방식입니다."
|
||||||
|
guide_example = "권장 예시: https://rp.example.com/.well-known/jwks.json"
|
||||||
|
guide_intro = "JWKS URI는 Baron이 만드는 값이 아니라 RP backend가 공개키를 노출하는 URL입니다."
|
||||||
|
guide_step_1 = "RP 서버에서 key pair를 생성하고 private key는 RP backend에만 보관합니다."
|
||||||
|
guide_step_2 = "RP backend가 public key를 JWKS(JSON Web Key Set) 형태로 제공하는 endpoint를 준비합니다."
|
||||||
|
guide_step_3 = "예: https://rp.example.com/.well-known/jwks.json 같은 URL을 DevFront에 입력합니다."
|
||||||
|
headless_help = "애플리케이션 고유의 디자인으로 로그인 화면을 구성할 수 있습니다. 실제 아이디/비밀번호 확인 및 보안 검증 로직은 Baron API를 통해 백그라운드에서 처리됩니다."
|
||||||
|
jwks_inline_help = "SSH-RSA 공개키 형식을 우선 권장합니다. 'ssh-rsa AAA...' 형식으로 입력하면 Baron이 OIDC 표준인 JWKS(JSON)로 자동 변환하여 저장합니다."
|
||||||
|
jwks_uri_help = "RP backend가 제공하는 공개키 endpoint URL을 입력하세요. 예: https://rp.example.com/.well-known/jwks.json"
|
||||||
|
request_object_alg_help = "Headless Login을 사용할 때 JAR(Request Object) 서명 알고리즘을 명시합니다."
|
||||||
|
source_help = "애플리케이션의 공개키(SSH-RSA)를 직접 등록하거나, 운영 환경이라면 JWKS URI를 통해 자동으로 검증할 수 있습니다."
|
||||||
|
subtitle = "Trusted RP 판정에 필요한 공개키와 headless login 관련 설정을 관리합니다."
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key.validation]
|
||||||
|
headless_requires_alg = "Headless Login을 사용하려면 Request Object Signing Algorithm을 입력해야 합니다."
|
||||||
|
headless_requires_private_key_jwt = "Headless Login을 사용하려면 token endpoint auth method가 private_key_jwt여야 합니다."
|
||||||
|
headless_requires_public_key = "Headless Login을 사용하려면 JWKS URI가 필요합니다."
|
||||||
|
invalid_jwks_inline = "입력값이 유효한 JSON(JWKS) 형식이 아닙니다. SSH-RSA의 경우 'ssh-rsa'로 시작해야 합니다."
|
||||||
|
invalid_jwks_uri = "JWKS URI 형식이 올바르지 않습니다."
|
||||||
|
missing_jwks_inline = "공개키(SSH-RSA 또는 JWKS)를 입력해야 합니다."
|
||||||
|
missing_jwks_uri = "JWKS URI를 입력해야 합니다."
|
||||||
|
private_key_jwt_requires_public_key = "서명 키 기반 인증을 사용하려면 JWKS URI가 필요합니다."
|
||||||
|
|
||||||
[msg.userfront.audit]
|
[msg.userfront.audit]
|
||||||
date = "접속일자: {{value}}"
|
date = "접속일자: {{value}}"
|
||||||
device = "접속환경: {{value}}"
|
device = "접속환경: {{value}}"
|
||||||
@@ -1715,6 +1742,26 @@ title = "보안 설정"
|
|||||||
trusted_rp_enable = "Trusted RP (자체 로그인 UI 사용)"
|
trusted_rp_enable = "Trusted RP (자체 로그인 UI 사용)"
|
||||||
trusted_rp_enable_help = "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다."
|
trusted_rp_enable_help = "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다."
|
||||||
|
|
||||||
|
[ui.dev.clients.general.public_key]
|
||||||
|
auth_method = "Token Endpoint Auth Method"
|
||||||
|
auth_method_client_secret_basic = "client_secret_basic"
|
||||||
|
auth_method_none = "none"
|
||||||
|
auth_method_private_key_jwt = "서명 키 기반 인증"
|
||||||
|
guide_toggle = "JWKS URI 준비 가이드"
|
||||||
|
headless_disabled = "Headless Disabled"
|
||||||
|
headless_enabled = "Headless Enabled"
|
||||||
|
headless_toggle = "Headless Login"
|
||||||
|
jwks_inline = "SSH-RSA 또는 JWKS 공개키"
|
||||||
|
jwks_inline_placeholder = "'ssh-rsa AAA...' 형식의 공개키를 먼저 붙여넣으세요. 필요하면 JWKS (JSON)도 입력할 수 있습니다."
|
||||||
|
jwks_uri = "JWKS URI"
|
||||||
|
jwks_uri_placeholder = "https://rp.example.com/.well-known/jwks.json"
|
||||||
|
request_object_alg = "Request Object Signing Algorithm"
|
||||||
|
request_object_alg_placeholder = "RS256"
|
||||||
|
source = "Public Key Source"
|
||||||
|
source_uri = "JWKS URI"
|
||||||
|
title = "공개키 등록"
|
||||||
|
validation_title = "저장 전 확인 필요"
|
||||||
|
|
||||||
[ui.dev.dashboard.ops.card]
|
[ui.dev.dashboard.ops.card]
|
||||||
consent_revoked = "Consent 회수 건수"
|
consent_revoked = "Consent 회수 건수"
|
||||||
hydra_status = "Hydra 상태"
|
hydra_status = "Hydra 상태"
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ create = ""
|
|||||||
delete = ""
|
delete = ""
|
||||||
details = ""
|
details = ""
|
||||||
edit = ""
|
edit = ""
|
||||||
|
enabled = ""
|
||||||
export = ""
|
export = ""
|
||||||
fail = ""
|
fail = ""
|
||||||
go_home = ""
|
go_home = ""
|
||||||
@@ -229,6 +230,32 @@ delete_confirm = ""
|
|||||||
notice = ""
|
notice = ""
|
||||||
notice_detail = ""
|
notice_detail = ""
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key]
|
||||||
|
auth_method_client_secret_basic_help = ""
|
||||||
|
auth_method_none_help = ""
|
||||||
|
auth_method_private_key_jwt_help = ""
|
||||||
|
guide_example = ""
|
||||||
|
guide_intro = ""
|
||||||
|
guide_step_1 = ""
|
||||||
|
guide_step_2 = ""
|
||||||
|
guide_step_3 = ""
|
||||||
|
headless_help = ""
|
||||||
|
jwks_inline_help = ""
|
||||||
|
jwks_uri_help = ""
|
||||||
|
request_object_alg_help = ""
|
||||||
|
source_help = ""
|
||||||
|
subtitle = ""
|
||||||
|
|
||||||
|
[msg.dev.clients.general.public_key.validation]
|
||||||
|
headless_requires_alg = ""
|
||||||
|
headless_requires_private_key_jwt = ""
|
||||||
|
headless_requires_public_key = ""
|
||||||
|
invalid_jwks_inline = ""
|
||||||
|
invalid_jwks_uri = ""
|
||||||
|
missing_jwks_inline = ""
|
||||||
|
missing_jwks_uri = ""
|
||||||
|
private_key_jwt_requires_public_key = ""
|
||||||
|
|
||||||
[msg.userfront.audit]
|
[msg.userfront.audit]
|
||||||
date = ""
|
date = ""
|
||||||
device = ""
|
device = ""
|
||||||
@@ -1709,6 +1736,26 @@ title = ""
|
|||||||
trusted_rp_enable = ""
|
trusted_rp_enable = ""
|
||||||
trusted_rp_enable_help = ""
|
trusted_rp_enable_help = ""
|
||||||
|
|
||||||
|
[ui.dev.clients.general.public_key]
|
||||||
|
auth_method = ""
|
||||||
|
auth_method_client_secret_basic = ""
|
||||||
|
auth_method_none = ""
|
||||||
|
auth_method_private_key_jwt = ""
|
||||||
|
guide_toggle = ""
|
||||||
|
headless_disabled = ""
|
||||||
|
headless_enabled = ""
|
||||||
|
headless_toggle = ""
|
||||||
|
jwks_inline = ""
|
||||||
|
jwks_inline_placeholder = ""
|
||||||
|
jwks_uri = ""
|
||||||
|
jwks_uri_placeholder = ""
|
||||||
|
request_object_alg = ""
|
||||||
|
request_object_alg_placeholder = ""
|
||||||
|
source = ""
|
||||||
|
source_uri = ""
|
||||||
|
title = ""
|
||||||
|
validation_title = ""
|
||||||
|
|
||||||
[ui.dev.dashboard.ops.card]
|
[ui.dev.dashboard.ops.card]
|
||||||
consent_revoked = ""
|
consent_revoked = ""
|
||||||
hydra_status = ""
|
hydra_status = ""
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ create = "Create"
|
|||||||
delete = "Delete"
|
delete = "Delete"
|
||||||
details = "Details"
|
details = "Details"
|
||||||
edit = "Edit"
|
edit = "Edit"
|
||||||
|
enabled = "Enabled"
|
||||||
export = "Export"
|
export = "Export"
|
||||||
fail = "Fail"
|
fail = "Fail"
|
||||||
go_home = "Go Home"
|
go_home = "Go Home"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ create = "생성"
|
|||||||
delete = "삭제"
|
delete = "삭제"
|
||||||
details = "상세정보"
|
details = "상세정보"
|
||||||
edit = "편집"
|
edit = "편집"
|
||||||
|
enabled = "사용"
|
||||||
export = "내보내기"
|
export = "내보내기"
|
||||||
fail = "실패"
|
fail = "실패"
|
||||||
go_home = "홈으로"
|
go_home = "홈으로"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ create = ""
|
|||||||
delete = ""
|
delete = ""
|
||||||
details = ""
|
details = ""
|
||||||
edit = ""
|
edit = ""
|
||||||
|
enabled = ""
|
||||||
export = ""
|
export = ""
|
||||||
fail = ""
|
fail = ""
|
||||||
go_home = ""
|
go_home = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user