Server (NestJS): - AcadeniceAuditLogModule: GET /api/acadenice/audit-log (admin/owner, Kysely, paginated + filtered) - AcadeniceApiKeysModule: GET/POST/DELETE /api/acadenice/api-keys (JWT, bcrypt hash, acdk_ prefix) - AcadeniceSecurityModule: GET /api/acadenice/security/oidc-status (admin, no secrets exposed) - Migration 20260510T100000: acadenice_api_key table with token_hash + bcrypt - Permissions catalog: added audit_log:read Client (React 18 + Mantine v7): - Audit log page: paginated table with filters (event, userId, date range) - API keys page: list/create/revoke personal tokens, one-time display modal - Security/OIDC status page: read-only, env-var config reference - Sidebar rewired: Security & SSO, API keys, Audit log -> acadenice/* routes (no EE feature gates) - Prefetch functions for new routes Tests: 36 server (Jest) + client typecheck clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
import { Button, Group, Modal, Text } from "@mantine/core";
|
|
import { useTranslation } from "react-i18next";
|
|
import { AcadeniceApiKey } from "../types/api-key.types";
|
|
import { useRevokeAcadeniceApiKeyMutation } from "../queries/api-key.queries";
|
|
|
|
interface RevokeApiKeyModalProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
apiKey: AcadeniceApiKey | null;
|
|
}
|
|
|
|
export function AcadeniceRevokeApiKeyModal({
|
|
opened,
|
|
onClose,
|
|
apiKey,
|
|
}: RevokeApiKeyModalProps) {
|
|
const { t } = useTranslation();
|
|
const revokeMutation = useRevokeAcadeniceApiKeyMutation();
|
|
|
|
if (!apiKey) return null;
|
|
|
|
const handleRevoke = () => {
|
|
revokeMutation.mutate(apiKey.id, { onSuccess: onClose });
|
|
};
|
|
|
|
return (
|
|
<Modal opened={opened} onClose={onClose} title={t("Revoke API key")} size="sm">
|
|
<Text size="sm" mb="md">
|
|
{t(
|
|
'Are you sure you want to revoke "{{label}}"? This action cannot be undone.',
|
|
{ label: apiKey.label },
|
|
)}
|
|
</Text>
|
|
|
|
<Group justify="flex-end">
|
|
<Button variant="default" onClick={onClose}>
|
|
{t("Cancel")}
|
|
</Button>
|
|
<Button
|
|
color="red"
|
|
loading={revokeMutation.isPending}
|
|
onClick={handleRevoke}
|
|
aria-label={t("Confirm revoke API key")}
|
|
>
|
|
{t("Revoke")}
|
|
</Button>
|
|
</Group>
|
|
</Modal>
|
|
);
|
|
}
|