forked from baron/baron-sso
189 lines
6.5 KiB
TypeScript
189 lines
6.5 KiB
TypeScript
import { useMutation } from "@tanstack/react-query";
|
|
import { CheckCircle2, XCircle } 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";
|
|
import { t } from "../../../lib/i18n";
|
|
|
|
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="border-primary/20 bg-[var(--color-panel)]">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg font-bold">
|
|
{t("ui.admin.auth_guard.checker.title", "ReBAC permission checker")}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{t(
|
|
"ui.admin.auth_guard.checker.description",
|
|
"Check in real time whether a subject has access to a resource through 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>
|
|
{t("ui.admin.auth_guard.checker.namespace.label", "Namespace")}
|
|
</Label>
|
|
<select
|
|
id="permission-checker-namespace"
|
|
name="permission-checker-namespace"
|
|
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">
|
|
{t("ui.admin.auth_guard.checker.namespace.tenant", "Tenant")}
|
|
</option>
|
|
<option value="TenantGroup">
|
|
{t(
|
|
"ui.admin.auth_guard.checker.namespace.tenant_group",
|
|
"TenantGroup",
|
|
)}
|
|
</option>
|
|
<option value="RelyingParty">
|
|
{t(
|
|
"ui.admin.auth_guard.checker.namespace.relying_party",
|
|
"RelyingParty",
|
|
)}
|
|
</option>
|
|
<option value="System">
|
|
{t("ui.admin.auth_guard.checker.namespace.system", "System")}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>
|
|
{t("ui.admin.auth_guard.checker.relation", "Relation")}
|
|
</Label>
|
|
<Input
|
|
placeholder={t(
|
|
"ui.admin.auth_guard.checker.relation_placeholder",
|
|
"view, manage, admins...",
|
|
)}
|
|
value={relation}
|
|
onChange={(e) => setRelation(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>
|
|
{t("ui.admin.auth_guard.checker.object_id", "Object ID")}
|
|
</Label>
|
|
<Input
|
|
placeholder={t(
|
|
"ui.admin.auth_guard.checker.object_id_placeholder",
|
|
"Tenant UUID, etc.",
|
|
)}
|
|
value={object}
|
|
onChange={(e) => setObject(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>
|
|
{t("ui.admin.auth_guard.checker.subject", "Subject (User:ID)")}
|
|
</Label>
|
|
<Input
|
|
placeholder={t(
|
|
"ui.admin.auth_guard.checker.subject_placeholder",
|
|
"User:uuid or 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 px-12 md:w-auto"
|
|
>
|
|
{checkMutation.isPending
|
|
? t("ui.admin.auth_guard.checker.checking", "Checking...")
|
|
: t("ui.admin.auth_guard.checker.check", "Check permission")}
|
|
</Button>
|
|
</div>
|
|
|
|
{checkMutation.isSuccess && result && (
|
|
<div
|
|
className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 p-6 animate-in zoom-in duration-300 ${
|
|
result.allowed
|
|
? "border-green-500/50 bg-green-500/10 text-green-600"
|
|
: "border-destructive/50 bg-destructive/10 text-destructive"
|
|
}`}
|
|
>
|
|
{result.allowed ? (
|
|
<>
|
|
<CheckCircle2 size={48} />
|
|
<div className="text-lg font-bold">
|
|
{t("ui.admin.auth_guard.checker.allowed", "Access ALLOWED")}
|
|
</div>
|
|
<p className="text-center text-sm opacity-80">
|
|
{t(
|
|
"ui.admin.auth_guard.checker.allowed_description",
|
|
"The subject has access to the requested resource, including inherited permissions.",
|
|
)}
|
|
</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
<XCircle size={48} />
|
|
<div className="text-lg font-bold">
|
|
{t("ui.admin.auth_guard.checker.denied", "Access DENIED")}
|
|
</div>
|
|
<p className="text-center text-sm opacity-80">
|
|
{t(
|
|
"ui.admin.auth_guard.checker.denied_description",
|
|
"The subject does not have access to the requested resource.",
|
|
)}
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default PermissionChecker;
|