snipe-auth-rbac 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.cjs +5 -1
- package/dist/admin/index.cjs.map +1 -1
- package/dist/admin/index.js +5 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/react/index.cjs +23 -9
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +17 -1
- package/dist/react/index.d.ts +17 -1
- package/dist/react/index.js +24 -9
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/sql/0001_initial.sql +6 -1
package/dist/react/index.d.cts
CHANGED
|
@@ -16,7 +16,23 @@ interface AuthRbacContextValue {
|
|
|
16
16
|
resources: ResourceRegistry;
|
|
17
17
|
activeCompanyId: string | null;
|
|
18
18
|
setActiveCompany: (id: string | null) => void;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Re-fetches the profile.
|
|
21
|
+
*
|
|
22
|
+
* By default the call toggles `loading: true` while the request is in
|
|
23
|
+
* flight — matches `useState` ergonomics for adopters wiring a manual
|
|
24
|
+
* "Refresh roles" button. Pass `{ silent: true }` to keep `loading`
|
|
25
|
+
* false; the existing profile stays visible until the new one lands.
|
|
26
|
+
* Adopters who re-create their `fetcher` reference on auth-token
|
|
27
|
+
* rotation should not need this flag at all — the provider already
|
|
28
|
+
* runs internal fetcher-change refreshes silently (see CHANGELOG
|
|
29
|
+
* 0.6.5). It's exposed for cases like background poll loops or
|
|
30
|
+
* post-mutation reconciliation where the consumer wants to avoid
|
|
31
|
+
* flickering a loading state through their own UI.
|
|
32
|
+
*/
|
|
33
|
+
refresh: (opts?: {
|
|
34
|
+
silent?: boolean;
|
|
35
|
+
}) => Promise<void>;
|
|
20
36
|
resolver: AuthRbacClient;
|
|
21
37
|
}
|
|
22
38
|
interface AuthRbacProviderProps {
|
package/dist/react/index.d.ts
CHANGED
|
@@ -16,7 +16,23 @@ interface AuthRbacContextValue {
|
|
|
16
16
|
resources: ResourceRegistry;
|
|
17
17
|
activeCompanyId: string | null;
|
|
18
18
|
setActiveCompany: (id: string | null) => void;
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Re-fetches the profile.
|
|
21
|
+
*
|
|
22
|
+
* By default the call toggles `loading: true` while the request is in
|
|
23
|
+
* flight — matches `useState` ergonomics for adopters wiring a manual
|
|
24
|
+
* "Refresh roles" button. Pass `{ silent: true }` to keep `loading`
|
|
25
|
+
* false; the existing profile stays visible until the new one lands.
|
|
26
|
+
* Adopters who re-create their `fetcher` reference on auth-token
|
|
27
|
+
* rotation should not need this flag at all — the provider already
|
|
28
|
+
* runs internal fetcher-change refreshes silently (see CHANGELOG
|
|
29
|
+
* 0.6.5). It's exposed for cases like background poll loops or
|
|
30
|
+
* post-mutation reconciliation where the consumer wants to avoid
|
|
31
|
+
* flickering a loading state through their own UI.
|
|
32
|
+
*/
|
|
33
|
+
refresh: (opts?: {
|
|
34
|
+
silent?: boolean;
|
|
35
|
+
}) => Promise<void>;
|
|
20
36
|
resolver: AuthRbacClient;
|
|
21
37
|
}
|
|
22
38
|
interface AuthRbacProviderProps {
|
package/dist/react/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
useContext,
|
|
17
17
|
useEffect,
|
|
18
18
|
useMemo,
|
|
19
|
+
useRef,
|
|
19
20
|
useState
|
|
20
21
|
} from "react";
|
|
21
22
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -66,27 +67,41 @@ function AuthRbacProvider(props) {
|
|
|
66
67
|
},
|
|
67
68
|
[persist]
|
|
68
69
|
);
|
|
69
|
-
const
|
|
70
|
-
|
|
70
|
+
const fetcherRef = useRef(fetcher);
|
|
71
|
+
const activeCompanyIdRef = useRef(activeCompanyId);
|
|
72
|
+
const persistRef = useRef(persist);
|
|
73
|
+
fetcherRef.current = fetcher;
|
|
74
|
+
activeCompanyIdRef.current = activeCompanyId;
|
|
75
|
+
persistRef.current = persist;
|
|
76
|
+
const refresh = useCallback(async (opts) => {
|
|
77
|
+
const silent = opts?.silent === true;
|
|
78
|
+
if (!silent) {
|
|
79
|
+
setLoading(true);
|
|
80
|
+
}
|
|
71
81
|
setError(null);
|
|
72
82
|
try {
|
|
73
|
-
const next = await
|
|
83
|
+
const next = await fetcherRef.current.fetchProfile();
|
|
74
84
|
setProfile(next);
|
|
75
|
-
const
|
|
85
|
+
const currentCompanyId = activeCompanyIdRef.current;
|
|
86
|
+
const stillMember = currentCompanyId != null && next.memberships.some((m) => m.company_id === currentCompanyId);
|
|
76
87
|
if (!stillMember) {
|
|
77
88
|
const fallback = next.memberships[0]?.company_id ?? null;
|
|
78
89
|
setActiveCompanyState(fallback);
|
|
79
|
-
|
|
90
|
+
persistRef.current(fallback);
|
|
80
91
|
}
|
|
81
92
|
} catch (e) {
|
|
82
93
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
83
94
|
} finally {
|
|
84
|
-
|
|
95
|
+
if (!silent) {
|
|
96
|
+
setLoading(false);
|
|
97
|
+
}
|
|
85
98
|
}
|
|
86
|
-
}, [
|
|
99
|
+
}, []);
|
|
100
|
+
const hasFetchedOnceRef = useRef(false);
|
|
87
101
|
useEffect(() => {
|
|
88
|
-
void refresh();
|
|
89
|
-
|
|
102
|
+
void refresh({ silent: hasFetchedOnceRef.current });
|
|
103
|
+
hasFetchedOnceRef.current = true;
|
|
104
|
+
}, [fetcher, refresh]);
|
|
90
105
|
const resolver = useMemo(() => {
|
|
91
106
|
if (profile == null) {
|
|
92
107
|
return {
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/AuthRbacProvider.tsx","../../src/react/useCan.ts","../../src/react/useCanAccessSection.ts","../../src/react/Can.tsx","../../src/react/RequirePermission.tsx","../../src/react/useActiveCompany.ts","../../src/react/useFrontendConfig.ts","../../src/react/index.ts"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport {\n buildPermissionResolver,\n type AuthRbacClient,\n} from \"../client.js\";\nimport type {\n AuthRbacFetcher,\n PermissionMap,\n ResourceRegistry,\n UserProfile,\n} from \"../types.js\";\n\ninterface AuthRbacContextValue {\n /**\n * `null` means we haven't hydrated yet. Components should treat\n * the not-loaded state as \"no permissions\" (fail-closed).\n */\n profile: UserProfile | null;\n loading: boolean;\n error: Error | null;\n resources: ResourceRegistry;\n activeCompanyId: string | null;\n setActiveCompany: (id: string | null) => void;\n refresh: () => Promise<void>;\n resolver: AuthRbacClient;\n}\n\nconst AuthRbacContext = createContext<AuthRbacContextValue | null>(null);\n\nexport interface AuthRbacProviderProps {\n fetcher: AuthRbacFetcher;\n resources: ResourceRegistry;\n /**\n * Initial active company. Common patterns:\n * - read from URL query/path\n * - read from localStorage\n * - omit and let the user pick from the switcher\n */\n initialCompanyId?: string | null;\n /**\n * Persistence hook. Called every time the active company changes.\n * Default: writes to `localStorage` under\n * `auth-rbac:active-company`. Pass `false` to disable.\n */\n persistActiveCompany?:\n | ((id: string | null) => void)\n | false;\n children: ReactNode;\n}\n\nconst STORAGE_KEY = \"auth-rbac:active-company\";\n\nconst defaultPersist = (id: string | null) => {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n if (id == null) {\n window.localStorage.removeItem(STORAGE_KEY);\n } else {\n window.localStorage.setItem(STORAGE_KEY, id);\n }\n } catch {\n // localStorage may be unavailable (private browsing, SSR) —\n // fall back to in-memory only.\n }\n};\n\nconst readPersisted = (): string | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n};\n\nexport function AuthRbacProvider(props: AuthRbacProviderProps) {\n const { fetcher, resources, initialCompanyId, persistActiveCompany } = props;\n\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const [activeCompanyId, setActiveCompanyState] = useState<string | null>(\n initialCompanyId ?? readPersisted(),\n );\n\n const persist = useMemo(() => {\n if (persistActiveCompany === false) {\n return () => {};\n }\n return persistActiveCompany ?? defaultPersist;\n }, [persistActiveCompany]);\n\n const setActiveCompany = useCallback(\n (id: string | null) => {\n setActiveCompanyState(id);\n persist(id);\n },\n [persist],\n );\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const next = await fetcher.fetchProfile();\n setProfile(next);\n // If the persisted company isn't a membership, fall back to\n // the first one (or null for users with no memberships).\n const stillMember =\n activeCompanyId != null &&\n next.memberships.some((m) => m.company_id === activeCompanyId);\n if (!stillMember) {\n const fallback =\n next.memberships[0]?.company_id ?? null;\n setActiveCompanyState(fallback);\n persist(fallback);\n }\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetcher]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const resolver = useMemo<AuthRbacClient>(() => {\n if (profile == null) {\n // Empty resolver until the profile lands. Always returns\n // false → guards fall through to the unauthenticated branch.\n return {\n can: () => false,\n canAccessSection: () => false,\n activePermissions: () => ({}) as PermissionMap,\n systemPermissions: () => ({}) as PermissionMap,\n };\n }\n return buildPermissionResolver(resources, profile, activeCompanyId);\n }, [profile, resources, activeCompanyId]);\n\n const value = useMemo<AuthRbacContextValue>(\n () => ({\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n }),\n [\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n ],\n );\n\n return (\n <AuthRbacContext.Provider value={value}>\n {props.children}\n </AuthRbacContext.Provider>\n );\n}\n\nexport function useAuthRbac(): AuthRbacContextValue {\n const ctx = useContext(AuthRbacContext);\n if (!ctx) {\n throw new Error(\n \"useAuthRbac must be used within an <AuthRbacProvider> — wrap your app at the root.\",\n );\n }\n return ctx;\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Boolean permission check.\n *\n * @example\n * const canEdit = useCan(\"properties\", \"update\");\n * <Button disabled={!canEdit}>Speichern</Button>\n *\n * @example explicitly target a non-active company\n * const canRead = useCan(\"payments\", \"read\", { companyId: targetId });\n */\nexport function useCan(\n resource: string,\n action: Action,\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.can(resource, action, options);\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Direct-grant check, for sidebar items and list-page route guards.\n *\n * Returns `true` only when the action is granted on the resource as\n * a **direct** admin grant (no `<action>_granted_via`). Rows that\n * exist only because a parent resource's `dependsOn` cascade\n * materialised them return `false` here — even though\n * `useCan(resource, action)` would return `true` for the same role.\n *\n * Use case: a Verwalter with only `leases:read` direct should see\n * Mietverträge in the sidebar but **not** Mieter / Einheiten /\n * Liegenschaften — those reads are implied so the lease detail page\n * can render, not because the role should navigate to them as\n * top-level sections.\n *\n * @example sidebar item filtering\n * const showLeasesInSidebar = useCanAccessSection(\"leases\");\n * const showUnitsInSidebar = useCanAccessSection(\"units\");\n *\n * @example list-route gating\n * if (!useCanAccessSection(\"units\")) return <Forbidden />;\n *\n * Available since 0.4.0. With older SQL that doesn't return\n * `direct_*` maps, this always answers `false` — adopters still on\n * pre-0.4.0 SQL should keep using `useCan`.\n */\nexport function useCanAccessSection(\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.canAccessSection(resource, action, options);\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useCan } from \"./useCan.js\";\n\nexport interface CanProps extends CanOptions {\n resource: string;\n action: Action;\n /** Rendered when the user has the permission. */\n children: ReactNode;\n /**\n * Rendered when the user does NOT have the permission. Defaults\n * to `null` (silent hide). Pass a `<NoPermissionView />` or a\n * tooltip-wrapper to surface the denial explicitly.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Subtree gate. Bails before children render so any data fetching\n * inside `children` is skipped for users without permission.\n */\nexport function Can(props: CanProps) {\n const { resource, action, companyId, children, fallback = null } = props;\n const allowed = useCan(resource, action, { companyId });\n return <>{allowed ? children : fallback}</>;\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\nimport { useCan } from \"./useCan.js\";\n\nexport interface RequirePermissionProps extends CanOptions {\n resource: string;\n action: Action;\n /**\n * What to render while the profile is still loading. Defaults to\n * `null` (no flash) — pass a spinner if your routes typically\n * mount before the profile lands.\n */\n loadingFallback?: ReactNode;\n /**\n * What to render when access is denied. Defaults to a minimal\n * \"Sie haben keinen Zugriff\" message; pass your own component to\n * theme it.\n */\n deniedFallback?: ReactNode;\n /**\n * For `react-router-dom v6` route-element usage, pass an `<Outlet />`\n * here — the gate resolves to either the outlet or the denied\n * fallback. For component-tree usage, pass any children.\n */\n children?: ReactNode;\n}\n\n/**\n * Route- or component-level guard. Three render branches:\n *\n * - profile not yet loaded → `loadingFallback`\n * - permission denied → `deniedFallback`\n * - permission granted → `children`\n *\n * Drop-in replacement for the legacy `<RequireRolesRoute>` pattern.\n *\n * @example\n * // App.tsx route table\n * <Route element={\n * <RequirePermission resource=\"payments\" action=\"read\">\n * <Outlet />\n * </RequirePermission>\n * }>\n * <Route path=\"/payments\" element={<PaymentsPage />} />\n * </Route>\n */\nexport function RequirePermission(props: RequirePermissionProps) {\n const {\n resource,\n action,\n companyId,\n loadingFallback = null,\n deniedFallback = (\n <div role=\"alert\" style={{ padding: 24 }}>\n <strong>Sie haben keinen Zugriff.</strong>\n </div>\n ),\n children = null,\n } = props;\n\n const { profile, loading } = useAuthRbac();\n const allowed = useCan(resource, action, { companyId });\n\n if (loading || profile == null) {\n return <>{loadingFallback}</>;\n }\n if (!allowed) {\n return <>{deniedFallback}</>;\n }\n return <>{children}</>;\n}\n","import { useMemo } from \"react\";\n\nimport type { CompanyMembership } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\nexport interface ActiveCompany {\n id: string | null;\n membership: CompanyMembership | null;\n memberships: CompanyMembership[];\n setActive: (id: string | null) => void;\n}\n\n/**\n * Read + switch the active company.\n *\n * @example\n * const { id, memberships, setActive } = useActiveCompany();\n *\n * return (\n * <select value={id ?? \"\"} onChange={(e) => setActive(e.target.value || null)}>\n * {memberships.map((m) => (\n * <option key={m.company_id} value={m.company_id}>{m.company_name}</option>\n * ))}\n * </select>\n * );\n */\nexport function useActiveCompany(): ActiveCompany {\n const { profile, activeCompanyId, setActiveCompany } = useAuthRbac();\n\n return useMemo(() => {\n const memberships = profile?.memberships ?? [];\n const membership =\n memberships.find((m) => m.company_id === activeCompanyId) ?? null;\n return {\n id: activeCompanyId,\n membership,\n memberships,\n setActive: setActiveCompany,\n };\n }, [profile, activeCompanyId, setActiveCompany]);\n}\n","import { useMemo } from \"react\";\n\nimport type { FrontendConfig } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Reads the merged `frontend_config` for the user. Sources in\n * priority order: active company's membership > system roles. Use\n * this to drive sidebar items, dashboard defaults, and any other\n * \"what should this role see\" UX without hardcoded role checks.\n *\n * The shape is intentionally `Record<string, unknown>` — your host\n * project owns the schema. Document your keys (e.g. `sidebar`,\n * `default_dashboard`) once and stick to them.\n */\nexport function useFrontendConfig(): FrontendConfig {\n const { profile, activeCompanyId } = useAuthRbac();\n return useMemo(() => {\n if (!profile) {\n return {};\n }\n const membershipConfig =\n profile.memberships.find((m) => m.company_id === activeCompanyId)\n ?.frontend_config ?? {};\n return { ...profile.system_frontend_config, ...membershipConfig };\n }, [profile, activeCompanyId]);\n}\n","/**\n * React + Next.js entry. Import this in browser code:\n *\n * import { AuthRbacProvider, useCan } from \"snipe-auth-rbac/react\";\n *\n * The non-React entry (`snipe-auth-rbac`) re-exports types and the\n * pure resolver, suitable for Node, edge workers, and tests.\n */\n\nexport {\n AuthRbacProvider,\n useAuthRbac,\n type AuthRbacProviderProps,\n} from \"./AuthRbacProvider.js\";\n\nexport { useCan } from \"./useCan.js\";\nexport { useCanAccessSection } from \"./useCanAccessSection.js\";\nexport { Can, type CanProps } from \"./Can.js\";\nexport {\n RequirePermission,\n type RequirePermissionProps,\n} from \"./RequirePermission.js\";\nexport { useActiveCompany, type ActiveCompany } from \"./useActiveCompany.js\";\nexport { useFrontendConfig } from \"./useFrontendConfig.js\";\n\n// Re-exports for convenience so consumers don't need two imports.\nexport type {\n Action,\n AuthRbacFetcher,\n CompanyMembership,\n DependencyEdge,\n DirectGrantMap,\n FrontendConfig,\n PermissionGrid,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n RoleSummary,\n UserProfile,\n} from \"../types.js\";\n\nexport { RbacRegistryError } from \"../define.js\";\n\nexport {\n createSupabaseFetcher,\n createHttpFetcher,\n detectRbacSchema,\n} from \"../fetchers.js\";\n\nimport { defineAuthRbac as _defineAuthRbac } from \"../define.js\";\nimport { Can } from \"./Can.js\";\nimport { RequirePermission } from \"./RequirePermission.js\";\nimport { useCan } from \"./useCan.js\";\nimport { useCanAccessSection } from \"./useCanAccessSection.js\";\n\nimport type { ResourceDescriptor } from \"../types.js\";\nimport type { TypedGuards } from \"../define.js\";\n\n/**\n * Typed factory — pass a const-asserted resource registry and get\n * back guards whose `resource` arg is constrained to the registered\n * names. Recommended at the top of every host project.\n *\n * See ../define.ts for the full doc + example.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(resources: Reg): TypedGuards<Reg[number][\"resource\"]> {\n return _defineAuthRbac(resources, {\n useCan,\n useCanAccessSection,\n Can,\n RequirePermission,\n });\n}\n\nexport type { TypedGuards } from \"../define.js\";\n"],"mappings":";;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA6KH;AAjJJ,IAAM,kBAAkB,cAA2C,IAAI;AAuBvE,IAAM,cAAc;AAEpB,IAAM,iBAAiB,CAAC,OAAsB;AAC5C,MAAI,OAAO,WAAW,aAAa;AACjC;AAAA,EACF;AACA,MAAI;AACF,QAAI,MAAM,MAAM;AACd,aAAO,aAAa,WAAW,WAAW;AAAA,IAC5C,OAAO;AACL,aAAO,aAAa,QAAQ,aAAa,EAAE;AAAA,IAC7C;AAAA,EACF,QAAQ;AAAA,EAGR;AACF;AAEA,IAAM,gBAAgB,MAAqB;AACzC,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,OAAO,aAAa,QAAQ,WAAW;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,iBAAiB,OAA8B;AAC7D,QAAM,EAAE,SAAS,WAAW,kBAAkB,qBAAqB,IAAI;AAEvE,QAAM,CAAC,SAAS,UAAU,IAAI,SAA6B,IAAI;AAC/D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,CAAC,iBAAiB,qBAAqB,IAAI;AAAA,IAC/C,oBAAoB,cAAc;AAAA,EACpC;AAEA,QAAM,UAAU,QAAQ,MAAM;AAC5B,QAAI,yBAAyB,OAAO;AAClC,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AACA,WAAO,wBAAwB;AAAA,EACjC,GAAG,CAAC,oBAAoB,CAAC;AAEzB,QAAM,mBAAmB;AAAA,IACvB,CAAC,OAAsB;AACrB,4BAAsB,EAAE;AACxB,cAAQ,EAAE;AAAA,IACZ;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AAEA,QAAM,UAAU,YAAY,YAAY;AACtC,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,aAAa;AACxC,iBAAW,IAAI;AAGf,YAAM,cACJ,mBAAmB,QACnB,KAAK,YAAY,KAAK,CAAC,MAAM,EAAE,eAAe,eAAe;AAC/D,UAAI,CAAC,aAAa;AAChB,cAAM,WACJ,KAAK,YAAY,CAAC,GAAG,cAAc;AACrC,8BAAsB,QAAQ;AAC9B,gBAAQ,QAAQ;AAAA,MAClB;AAAA,IACF,SAAS,GAAG;AACV,eAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,IACxD,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EAEF,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,SAAK,QAAQ;AAAA,EACf,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,WAAW,QAAwB,MAAM;AAC7C,QAAI,WAAW,MAAM;AAGnB,aAAO;AAAA,QACL,KAAK,MAAM;AAAA,QACX,kBAAkB,MAAM;AAAA,QACxB,mBAAmB,OAAO,CAAC;AAAA,QAC3B,mBAAmB,OAAO,CAAC;AAAA,MAC7B;AAAA,IACF;AACA,WAAO,wBAAwB,WAAW,SAAS,eAAe;AAAA,EACpE,GAAG,CAAC,SAAS,WAAW,eAAe,CAAC;AAExC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE,oBAAC,gBAAgB,UAAhB,EAAyB,OACvB,gBAAM,UACT;AAEJ;AAEO,SAAS,cAAoC;AAClD,QAAM,MAAM,WAAW,eAAe;AACtC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACpLO,SAAS,OACd,UACA,QACA,SACS;AACT,QAAM,EAAE,SAAS,IAAI,YAAY;AACjC,SAAO,SAAS,IAAI,UAAU,QAAQ,OAAO;AAC/C;;;ACSO,SAAS,oBACd,UACA,SAAiB,QACjB,SACS;AACT,QAAM,EAAE,SAAS,IAAI,YAAY;AACjC,SAAO,SAAS,iBAAiB,UAAU,QAAQ,OAAO;AAC5D;;;ACXS,0BAAAA,YAAA;AAHF,SAAS,IAAI,OAAiB;AACnC,QAAM,EAAE,UAAU,QAAQ,WAAW,UAAU,WAAW,KAAK,IAAI;AACnE,QAAM,UAAU,OAAO,UAAU,QAAQ,EAAE,UAAU,CAAC;AACtD,SAAO,gBAAAA,KAAA,YAAG,oBAAU,WAAW,UAAS;AAC1C;;;AC8BQ,SAUG,YAAAC,WAVH,OAAAC,YAAA;AARD,SAAS,kBAAkB,OAA+B;AAC/D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,iBACE,gBAAAA,KAAC,SAAI,MAAK,SAAQ,OAAO,EAAE,SAAS,GAAG,GACrC,0BAAAA,KAAC,YAAO,uCAAyB,GACnC;AAAA,IAEF,WAAW;AAAA,EACb,IAAI;AAEJ,QAAM,EAAE,SAAS,QAAQ,IAAI,YAAY;AACzC,QAAM,UAAU,OAAO,UAAU,QAAQ,EAAE,UAAU,CAAC;AAEtD,MAAI,WAAW,WAAW,MAAM;AAC9B,WAAO,gBAAAA,KAAAD,WAAA,EAAG,2BAAgB;AAAA,EAC5B;AACA,MAAI,CAAC,SAAS;AACZ,WAAO,gBAAAC,KAAAD,WAAA,EAAG,0BAAe;AAAA,EAC3B;AACA,SAAO,gBAAAC,KAAAD,WAAA,EAAG,UAAS;AACrB;;;AC1EA,SAAS,WAAAE,gBAAe;AA2BjB,SAAS,mBAAkC;AAChD,QAAM,EAAE,SAAS,iBAAiB,iBAAiB,IAAI,YAAY;AAEnE,SAAOC,SAAQ,MAAM;AACnB,UAAM,cAAc,SAAS,eAAe,CAAC;AAC7C,UAAM,aACJ,YAAY,KAAK,CAAC,MAAM,EAAE,eAAe,eAAe,KAAK;AAC/D,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,WAAW;AAAA,IACb;AAAA,EACF,GAAG,CAAC,SAAS,iBAAiB,gBAAgB,CAAC;AACjD;;;ACzCA,SAAS,WAAAC,gBAAe;AAgBjB,SAAS,oBAAoC;AAClD,QAAM,EAAE,SAAS,gBAAgB,IAAI,YAAY;AACjD,SAAOC,SAAQ,MAAM;AACnB,QAAI,CAAC,SAAS;AACZ,aAAO,CAAC;AAAA,IACV;AACA,UAAM,mBACJ,QAAQ,YAAY,KAAK,CAAC,MAAM,EAAE,eAAe,eAAe,GAC5D,mBAAmB,CAAC;AAC1B,WAAO,EAAE,GAAG,QAAQ,wBAAwB,GAAG,iBAAiB;AAAA,EAClE,GAAG,CAAC,SAAS,eAAe,CAAC;AAC/B;;;ACuCO,SAASC,gBAEd,WAAsD;AACtD,SAAO,eAAgB,WAAW;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;","names":["jsx","Fragment","jsx","useMemo","useMemo","useMemo","useMemo","defineAuthRbac"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/AuthRbacProvider.tsx","../../src/react/useCan.ts","../../src/react/useCanAccessSection.ts","../../src/react/Can.tsx","../../src/react/RequirePermission.tsx","../../src/react/useActiveCompany.ts","../../src/react/useFrontendConfig.ts","../../src/react/index.ts"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport {\n buildPermissionResolver,\n type AuthRbacClient,\n} from \"../client.js\";\nimport type {\n AuthRbacFetcher,\n PermissionMap,\n ResourceRegistry,\n UserProfile,\n} from \"../types.js\";\n\ninterface AuthRbacContextValue {\n /**\n * `null` means we haven't hydrated yet. Components should treat\n * the not-loaded state as \"no permissions\" (fail-closed).\n */\n profile: UserProfile | null;\n loading: boolean;\n error: Error | null;\n resources: ResourceRegistry;\n activeCompanyId: string | null;\n setActiveCompany: (id: string | null) => void;\n /**\n * Re-fetches the profile.\n *\n * By default the call toggles `loading: true` while the request is in\n * flight — matches `useState` ergonomics for adopters wiring a manual\n * \"Refresh roles\" button. Pass `{ silent: true }` to keep `loading`\n * false; the existing profile stays visible until the new one lands.\n * Adopters who re-create their `fetcher` reference on auth-token\n * rotation should not need this flag at all — the provider already\n * runs internal fetcher-change refreshes silently (see CHANGELOG\n * 0.6.5). It's exposed for cases like background poll loops or\n * post-mutation reconciliation where the consumer wants to avoid\n * flickering a loading state through their own UI.\n */\n refresh: (opts?: { silent?: boolean }) => Promise<void>;\n resolver: AuthRbacClient;\n}\n\nconst AuthRbacContext = createContext<AuthRbacContextValue | null>(null);\n\nexport interface AuthRbacProviderProps {\n fetcher: AuthRbacFetcher;\n resources: ResourceRegistry;\n /**\n * Initial active company. Common patterns:\n * - read from URL query/path\n * - read from localStorage\n * - omit and let the user pick from the switcher\n */\n initialCompanyId?: string | null;\n /**\n * Persistence hook. Called every time the active company changes.\n * Default: writes to `localStorage` under\n * `auth-rbac:active-company`. Pass `false` to disable.\n */\n persistActiveCompany?:\n | ((id: string | null) => void)\n | false;\n children: ReactNode;\n}\n\nconst STORAGE_KEY = \"auth-rbac:active-company\";\n\nconst defaultPersist = (id: string | null) => {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n if (id == null) {\n window.localStorage.removeItem(STORAGE_KEY);\n } else {\n window.localStorage.setItem(STORAGE_KEY, id);\n }\n } catch {\n // localStorage may be unavailable (private browsing, SSR) —\n // fall back to in-memory only.\n }\n};\n\nconst readPersisted = (): string | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n};\n\nexport function AuthRbacProvider(props: AuthRbacProviderProps) {\n const { fetcher, resources, initialCompanyId, persistActiveCompany } = props;\n\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const [activeCompanyId, setActiveCompanyState] = useState<string | null>(\n initialCompanyId ?? readPersisted(),\n );\n\n const persist = useMemo(() => {\n if (persistActiveCompany === false) {\n return () => {};\n }\n return persistActiveCompany ?? defaultPersist;\n }, [persistActiveCompany]);\n\n const setActiveCompany = useCallback(\n (id: string | null) => {\n setActiveCompanyState(id);\n persist(id);\n },\n [persist],\n );\n\n // Hold the inputs `refresh` reads in refs so the callback identity\n // stays stable across renders. Previously `refresh` was rebuilt\n // whenever `fetcher` changed, which in turn churned the context\n // value memo and triggered downstream re-renders / re-mounts in\n // adopter route gates and dialogs. With refs, the callback has\n // empty deps and a permanent identity for the provider's lifetime.\n const fetcherRef = useRef(fetcher);\n const activeCompanyIdRef = useRef(activeCompanyId);\n const persistRef = useRef(persist);\n fetcherRef.current = fetcher;\n activeCompanyIdRef.current = activeCompanyId;\n persistRef.current = persist;\n\n const refresh = useCallback(async (opts?: { silent?: boolean }) => {\n const silent = opts?.silent === true;\n if (!silent) {\n setLoading(true);\n }\n setError(null);\n try {\n const next = await fetcherRef.current.fetchProfile();\n setProfile(next);\n // If the persisted company isn't a membership, fall back to\n // the first one (or null for users with no memberships).\n const currentCompanyId = activeCompanyIdRef.current;\n const stillMember =\n currentCompanyId != null &&\n next.memberships.some((m) => m.company_id === currentCompanyId);\n if (!stillMember) {\n const fallback =\n next.memberships[0]?.company_id ?? null;\n setActiveCompanyState(fallback);\n persistRef.current(fallback);\n }\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n if (!silent) {\n setLoading(false);\n }\n }\n }, []);\n\n // Track the first run so we can flip `loading: true` exactly once on\n // mount. Subsequent fires (e.g. the adopter swapped in a new fetcher\n // reference after auth-token rotation) happen silently — the old\n // profile keeps serving permissions until the new one lands, which\n // prevents route gates from flickering through their loading branch\n // and tearing down portaled dialogs / in-progress forms.\n const hasFetchedOnceRef = useRef(false);\n useEffect(() => {\n void refresh({ silent: hasFetchedOnceRef.current });\n hasFetchedOnceRef.current = true;\n }, [fetcher, refresh]);\n\n const resolver = useMemo<AuthRbacClient>(() => {\n if (profile == null) {\n // Empty resolver until the profile lands. Always returns\n // false → guards fall through to the unauthenticated branch.\n return {\n can: () => false,\n canAccessSection: () => false,\n activePermissions: () => ({}) as PermissionMap,\n systemPermissions: () => ({}) as PermissionMap,\n };\n }\n return buildPermissionResolver(resources, profile, activeCompanyId);\n }, [profile, resources, activeCompanyId]);\n\n const value = useMemo<AuthRbacContextValue>(\n () => ({\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n }),\n [\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n ],\n );\n\n return (\n <AuthRbacContext.Provider value={value}>\n {props.children}\n </AuthRbacContext.Provider>\n );\n}\n\nexport function useAuthRbac(): AuthRbacContextValue {\n const ctx = useContext(AuthRbacContext);\n if (!ctx) {\n throw new Error(\n \"useAuthRbac must be used within an <AuthRbacProvider> — wrap your app at the root.\",\n );\n }\n return ctx;\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Boolean permission check.\n *\n * @example\n * const canEdit = useCan(\"properties\", \"update\");\n * <Button disabled={!canEdit}>Speichern</Button>\n *\n * @example explicitly target a non-active company\n * const canRead = useCan(\"payments\", \"read\", { companyId: targetId });\n */\nexport function useCan(\n resource: string,\n action: Action,\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.can(resource, action, options);\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Direct-grant check, for sidebar items and list-page route guards.\n *\n * Returns `true` only when the action is granted on the resource as\n * a **direct** admin grant (no `<action>_granted_via`). Rows that\n * exist only because a parent resource's `dependsOn` cascade\n * materialised them return `false` here — even though\n * `useCan(resource, action)` would return `true` for the same role.\n *\n * Use case: a Verwalter with only `leases:read` direct should see\n * Mietverträge in the sidebar but **not** Mieter / Einheiten /\n * Liegenschaften — those reads are implied so the lease detail page\n * can render, not because the role should navigate to them as\n * top-level sections.\n *\n * @example sidebar item filtering\n * const showLeasesInSidebar = useCanAccessSection(\"leases\");\n * const showUnitsInSidebar = useCanAccessSection(\"units\");\n *\n * @example list-route gating\n * if (!useCanAccessSection(\"units\")) return <Forbidden />;\n *\n * Available since 0.4.0. With older SQL that doesn't return\n * `direct_*` maps, this always answers `false` — adopters still on\n * pre-0.4.0 SQL should keep using `useCan`.\n */\nexport function useCanAccessSection(\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.canAccessSection(resource, action, options);\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useCan } from \"./useCan.js\";\n\nexport interface CanProps extends CanOptions {\n resource: string;\n action: Action;\n /** Rendered when the user has the permission. */\n children: ReactNode;\n /**\n * Rendered when the user does NOT have the permission. Defaults\n * to `null` (silent hide). Pass a `<NoPermissionView />` or a\n * tooltip-wrapper to surface the denial explicitly.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Subtree gate. Bails before children render so any data fetching\n * inside `children` is skipped for users without permission.\n */\nexport function Can(props: CanProps) {\n const { resource, action, companyId, children, fallback = null } = props;\n const allowed = useCan(resource, action, { companyId });\n return <>{allowed ? children : fallback}</>;\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\nimport { useCan } from \"./useCan.js\";\n\nexport interface RequirePermissionProps extends CanOptions {\n resource: string;\n action: Action;\n /**\n * What to render while the profile is still loading. Defaults to\n * `null` (no flash) — pass a spinner if your routes typically\n * mount before the profile lands.\n */\n loadingFallback?: ReactNode;\n /**\n * What to render when access is denied. Defaults to a minimal\n * \"Sie haben keinen Zugriff\" message; pass your own component to\n * theme it.\n */\n deniedFallback?: ReactNode;\n /**\n * For `react-router-dom v6` route-element usage, pass an `<Outlet />`\n * here — the gate resolves to either the outlet or the denied\n * fallback. For component-tree usage, pass any children.\n */\n children?: ReactNode;\n}\n\n/**\n * Route- or component-level guard. Three render branches:\n *\n * - profile not yet loaded → `loadingFallback`\n * - permission denied → `deniedFallback`\n * - permission granted → `children`\n *\n * Drop-in replacement for the legacy `<RequireRolesRoute>` pattern.\n *\n * @example\n * // App.tsx route table\n * <Route element={\n * <RequirePermission resource=\"payments\" action=\"read\">\n * <Outlet />\n * </RequirePermission>\n * }>\n * <Route path=\"/payments\" element={<PaymentsPage />} />\n * </Route>\n */\nexport function RequirePermission(props: RequirePermissionProps) {\n const {\n resource,\n action,\n companyId,\n loadingFallback = null,\n deniedFallback = (\n <div role=\"alert\" style={{ padding: 24 }}>\n <strong>Sie haben keinen Zugriff.</strong>\n </div>\n ),\n children = null,\n } = props;\n\n const { profile, loading } = useAuthRbac();\n const allowed = useCan(resource, action, { companyId });\n\n if (loading || profile == null) {\n return <>{loadingFallback}</>;\n }\n if (!allowed) {\n return <>{deniedFallback}</>;\n }\n return <>{children}</>;\n}\n","import { useMemo } from \"react\";\n\nimport type { CompanyMembership } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\nexport interface ActiveCompany {\n id: string | null;\n membership: CompanyMembership | null;\n memberships: CompanyMembership[];\n setActive: (id: string | null) => void;\n}\n\n/**\n * Read + switch the active company.\n *\n * @example\n * const { id, memberships, setActive } = useActiveCompany();\n *\n * return (\n * <select value={id ?? \"\"} onChange={(e) => setActive(e.target.value || null)}>\n * {memberships.map((m) => (\n * <option key={m.company_id} value={m.company_id}>{m.company_name}</option>\n * ))}\n * </select>\n * );\n */\nexport function useActiveCompany(): ActiveCompany {\n const { profile, activeCompanyId, setActiveCompany } = useAuthRbac();\n\n return useMemo(() => {\n const memberships = profile?.memberships ?? [];\n const membership =\n memberships.find((m) => m.company_id === activeCompanyId) ?? null;\n return {\n id: activeCompanyId,\n membership,\n memberships,\n setActive: setActiveCompany,\n };\n }, [profile, activeCompanyId, setActiveCompany]);\n}\n","import { useMemo } from \"react\";\n\nimport type { FrontendConfig } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Reads the merged `frontend_config` for the user. Sources in\n * priority order: active company's membership > system roles. Use\n * this to drive sidebar items, dashboard defaults, and any other\n * \"what should this role see\" UX without hardcoded role checks.\n *\n * The shape is intentionally `Record<string, unknown>` — your host\n * project owns the schema. Document your keys (e.g. `sidebar`,\n * `default_dashboard`) once and stick to them.\n */\nexport function useFrontendConfig(): FrontendConfig {\n const { profile, activeCompanyId } = useAuthRbac();\n return useMemo(() => {\n if (!profile) {\n return {};\n }\n const membershipConfig =\n profile.memberships.find((m) => m.company_id === activeCompanyId)\n ?.frontend_config ?? {};\n return { ...profile.system_frontend_config, ...membershipConfig };\n }, [profile, activeCompanyId]);\n}\n","/**\n * React + Next.js entry. Import this in browser code:\n *\n * import { AuthRbacProvider, useCan } from \"snipe-auth-rbac/react\";\n *\n * The non-React entry (`snipe-auth-rbac`) re-exports types and the\n * pure resolver, suitable for Node, edge workers, and tests.\n */\n\nexport {\n AuthRbacProvider,\n useAuthRbac,\n type AuthRbacProviderProps,\n} from \"./AuthRbacProvider.js\";\n\nexport { useCan } from \"./useCan.js\";\nexport { useCanAccessSection } from \"./useCanAccessSection.js\";\nexport { Can, type CanProps } from \"./Can.js\";\nexport {\n RequirePermission,\n type RequirePermissionProps,\n} from \"./RequirePermission.js\";\nexport { useActiveCompany, type ActiveCompany } from \"./useActiveCompany.js\";\nexport { useFrontendConfig } from \"./useFrontendConfig.js\";\n\n// Re-exports for convenience so consumers don't need two imports.\nexport type {\n Action,\n AuthRbacFetcher,\n CompanyMembership,\n DependencyEdge,\n DirectGrantMap,\n FrontendConfig,\n PermissionGrid,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n RoleSummary,\n UserProfile,\n} from \"../types.js\";\n\nexport { RbacRegistryError } from \"../define.js\";\n\nexport {\n createSupabaseFetcher,\n createHttpFetcher,\n detectRbacSchema,\n} from \"../fetchers.js\";\n\nimport { defineAuthRbac as _defineAuthRbac } from \"../define.js\";\nimport { Can } from \"./Can.js\";\nimport { RequirePermission } from \"./RequirePermission.js\";\nimport { useCan } from \"./useCan.js\";\nimport { useCanAccessSection } from \"./useCanAccessSection.js\";\n\nimport type { ResourceDescriptor } from \"../types.js\";\nimport type { TypedGuards } from \"../define.js\";\n\n/**\n * Typed factory — pass a const-asserted resource registry and get\n * back guards whose `resource` arg is constrained to the registered\n * names. Recommended at the top of every host project.\n *\n * See ../define.ts for the full doc + example.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(resources: Reg): TypedGuards<Reg[number][\"resource\"]> {\n return _defineAuthRbac(resources, {\n useCan,\n useCanAccessSection,\n Can,\n RequirePermission,\n });\n}\n\nexport type { TypedGuards } from \"../define.js\";\n"],"mappings":"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","names":["jsx","Fragment","jsx","useMemo","useMemo","useMemo","useMemo","defineAuthRbac"]}
|
package/package.json
CHANGED
package/sql/0001_initial.sql
CHANGED
|
@@ -729,7 +729,12 @@ AS $$
|
|
|
729
729
|
DECLARE
|
|
730
730
|
v_count int;
|
|
731
731
|
BEGIN
|
|
732
|
-
DELETE
|
|
732
|
+
-- `WHERE true` is semantically identical to a bare DELETE but
|
|
733
|
+
-- bypasses Supabase / PostgREST's no-bare-DELETE safety guard,
|
|
734
|
+
-- which fires on RPC-invoked SECURITY DEFINER functions and
|
|
735
|
+
-- returns HTTP 400 "DELETE requires a WHERE clause". The analyzer
|
|
736
|
+
-- is textual, not semantic.
|
|
737
|
+
DELETE FROM rbac.resource_dependencies WHERE true;
|
|
733
738
|
INSERT INTO rbac.resource_dependencies (
|
|
734
739
|
parent_resource, child_resource, action
|
|
735
740
|
)
|