forked from baron/baron-sso
feat: 테넌트/RP 관리자 할당 UI 및 ReBAC 권한 검증 도구 구현 #244
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
CardTitle,
|
||||
} from "../../components/ui/card";
|
||||
import { t } from "../../lib/i18n";
|
||||
import PermissionChecker from "./components/PermissionChecker";
|
||||
|
||||
const summaryCards = [
|
||||
{
|
||||
@@ -216,6 +217,8 @@ function GlobalOverviewPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<PermissionChecker />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { ShieldAlert, CheckCircle2, XCircle, Search } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import { Label } from "../../../components/ui/label";
|
||||
import apiClient from "../../../lib/apiClient";
|
||||
|
||||
type CheckPermissionResponse = {
|
||||
allowed: boolean;
|
||||
query: {
|
||||
namespace: string;
|
||||
object: string;
|
||||
relation: string;
|
||||
subject: string;
|
||||
};
|
||||
};
|
||||
|
||||
function PermissionChecker() {
|
||||
const [namespace, setNamespace] = useState("Tenant");
|
||||
const [object, setObject] = useState("");
|
||||
const [relation, setRelation] = useState("manage");
|
||||
const [subject, setSubject] = useState("");
|
||||
|
||||
const checkMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.get<CheckPermissionResponse>("/v1/admin/debug/check-permission", {
|
||||
params: { namespace, object, relation, subject },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
const result = checkMutation.data;
|
||||
|
||||
return (
|
||||
<Card className="bg-[var(--color-panel)] border-primary/20">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ShieldAlert size={20} className="text-primary" />
|
||||
ReBAC 권한 검증 도구
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
특정 주체(Subject)가 특정 리소스(Object)에 대해 권한이 있는지 Ory Keto를 통해 실시간으로 확인합니다.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Namespace</Label>
|
||||
<select
|
||||
value={namespace}
|
||||
onChange={e => setNamespace(e.target.value)}
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
<option value="Tenant">Tenant</option>
|
||||
<option value="TenantGroup">TenantGroup</option>
|
||||
<option value="RelyingParty">RelyingParty</option>
|
||||
<option value="System">System</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Relation</Label>
|
||||
<Input
|
||||
placeholder="view, manage, admins..."
|
||||
value={relation}
|
||||
onChange={e => setRelation(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Object ID</Label>
|
||||
<Input
|
||||
placeholder="Tenant UUID 등"
|
||||
value={object}
|
||||
onChange={e => setObject(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Subject (User:ID)</Label>
|
||||
<Input
|
||||
placeholder="User:uuid 또는 Namespace:ID#Relation"
|
||||
value={subject}
|
||||
onChange={e => setSubject(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
onClick={() => checkMutation.mutate()}
|
||||
disabled={!object || !subject || checkMutation.isPending}
|
||||
className="w-full md:w-auto px-12"
|
||||
>
|
||||
{checkMutation.isPending ? "검증 중..." : "권한 확인 실행"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{checkMutation.isSuccess && (
|
||||
<div className={`p-6 rounded-xl border-2 flex flex-col items-center justify-center gap-3 animate-in zoom-in duration-300 ${
|
||||
result.allowed ? "bg-green-500/10 border-green-500/50 text-green-600" : "bg-destructive/10 border-destructive/50 text-destructive"
|
||||
}`}>
|
||||
{result.allowed ? (
|
||||
<>
|
||||
<CheckCircle2 size={48} />
|
||||
<div className="text-xl font-bold">Access ALLOWED</div>
|
||||
<p className="text-sm opacity-80 text-center">
|
||||
해당 사용자는 요청한 리소스에 대해 권한이 있습니다. (상속 포함)
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<XCircle size={48} />
|
||||
<div className="text-xl font-bold">Access DENIED</div>
|
||||
<p className="text-sm opacity-80 text-center">
|
||||
해당 사용자는 요청한 리소스에 대해 권한이 없습니다.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default PermissionChecker;
|
||||
Reference in New Issue
Block a user