1
0
forked from baron/baron-sso

Implement tenant import and RP auto login policies

This commit is contained in:
2026-04-30 15:45:34 +09:00
parent 24807eab0f
commit f7e4d43b16
76 changed files with 5307 additions and 441 deletions

View File

@@ -23,6 +23,11 @@ import {
updateTenant,
} from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
import { DomainTagInput } from "../components/DomainTagInput";
import {
formatDomainConflictMessage,
type ServerDomainConflict,
} from "../utils/domainTags";
export function TenantProfilePage() {
const { tenantId } = useParams<{ tenantId: string }>();
@@ -53,7 +58,10 @@ export function TenantProfilePage() {
const [slug, setSlug] = useState("");
const [description, setDescription] = useState("");
const [status, setStatus] = useState("active");
const [domains, setDomains] = useState("");
const [domains, setDomains] = useState<string[]>([]);
const [forceDomainConflicts, setForceDomainConflicts] = useState<string[]>(
[],
);
const [parentId, setParentId] = useState("");
useEffect(() => {
@@ -63,13 +71,14 @@ export function TenantProfilePage() {
setSlug(tenantQuery.data.slug);
setDescription(tenantQuery.data.description ?? "");
setStatus(tenantQuery.data.status);
setDomains(tenantQuery.data.domains?.join(", ") ?? "");
setDomains(tenantQuery.data.domains ?? []);
setForceDomainConflicts([]);
setParentId(tenantQuery.data.parentId ?? "");
}
}, [tenantQuery.data]);
const updateMutation = useMutation({
mutationFn: () =>
mutationFn: (overrideForceDomains?: string[]) =>
updateTenant(tenantId, {
name,
type,
@@ -77,17 +86,36 @@ export function TenantProfilePage() {
description: description || undefined,
status,
parentId: parentId || undefined,
domains: domains
.split(",")
.map((d) => d.trim())
.filter((d) => d !== ""),
domains,
forceDomainConflicts: overrideForceDomains ?? forceDomainConflicts,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tenants"] });
queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] });
toast.success(t("msg.info.saved_success", "저장되었습니다."));
},
onError: (err: AxiosError<{ error?: string }>) => {
onError: (
err: AxiosError<{
code?: string;
error?: string;
conflicts?: ServerDomainConflict[];
}>,
) => {
const conflicts = err.response?.data?.conflicts ?? [];
if (
err.response?.data?.code === "tenant_domain_conflict" &&
conflicts.length > 0
) {
const nextForceDomains = Array.from(
new Set([...forceDomainConflicts, ...conflicts.map((c) => c.domain)]),
);
const message = conflicts.map(formatDomainConflictMessage).join("\n");
if (window.confirm(message)) {
setForceDomainConflicts(nextForceDomains);
updateMutation.mutate(nextForceDomains);
}
return;
}
toast.error(
err.response?.data?.error ||
t("err.common.unknown", "오류가 발생했습니다."),
@@ -257,9 +285,14 @@ export function TenantProfilePage() {
"허용된 도메인 (콤마로 구분)",
)}
</Label>
<Input
<DomainTagInput
id="tenant-domains"
value={domains}
onChange={(e) => setDomains(e.target.value)}
onChange={setDomains}
tenants={parentQuery.data?.items ?? []}
currentTenantId={tenantId}
confirmedConflicts={forceDomainConflicts}
onConfirmedConflictsChange={setForceDomainConflicts}
placeholder="example.com, example.kr"
/>
<p className="text-xs text-muted-foreground">
@@ -322,7 +355,7 @@ export function TenantProfilePage() {
{t("ui.common.cancel", "취소")}
</Button>
<Button
onClick={() => updateMutation.mutate()}
onClick={() => updateMutation.mutate(undefined)}
disabled={
updateMutation.isPending ||
tenantQuery.isLoading ||