snipe-auth-rbac 0.1.0

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.
@@ -0,0 +1,221 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentType } from 'react';
3
+ import { AuthRbacClient, CanOptions } from '../index.js';
4
+ export { createHttpFetcher, createSupabaseFetcher } from '../index.js';
5
+ import { b as AuthRbacFetcher, c as ResourceRegistry, U as UserProfile, A as Action, C as CompanyMembership, F as FrontendConfig, a as ResourceDescriptor } from '../types-BEc5SCIo.js';
6
+ export { P as PermissionGrid, d as PermissionMap, R as ResourceScope, e as RoleSummary } from '../types-BEc5SCIo.js';
7
+
8
+ interface AuthRbacContextValue {
9
+ /**
10
+ * `null` means we haven't hydrated yet. Components should treat
11
+ * the not-loaded state as "no permissions" (fail-closed).
12
+ */
13
+ profile: UserProfile | null;
14
+ loading: boolean;
15
+ error: Error | null;
16
+ resources: ResourceRegistry;
17
+ activeCompanyId: string | null;
18
+ setActiveCompany: (id: string | null) => void;
19
+ refresh: () => Promise<void>;
20
+ resolver: AuthRbacClient;
21
+ }
22
+ interface AuthRbacProviderProps {
23
+ fetcher: AuthRbacFetcher;
24
+ resources: ResourceRegistry;
25
+ /**
26
+ * Initial active company. Common patterns:
27
+ * - read from URL query/path
28
+ * - read from localStorage
29
+ * - omit and let the user pick from the switcher
30
+ */
31
+ initialCompanyId?: string | null;
32
+ /**
33
+ * Persistence hook. Called every time the active company changes.
34
+ * Default: writes to `localStorage` under
35
+ * `auth-rbac:active-company`. Pass `false` to disable.
36
+ */
37
+ persistActiveCompany?: ((id: string | null) => void) | false;
38
+ children: ReactNode;
39
+ }
40
+ declare function AuthRbacProvider(props: AuthRbacProviderProps): react_jsx_runtime.JSX.Element;
41
+ declare function useAuthRbac(): AuthRbacContextValue;
42
+
43
+ /**
44
+ * Boolean permission check.
45
+ *
46
+ * @example
47
+ * const canEdit = useCan("properties", "update");
48
+ * <Button disabled={!canEdit}>Speichern</Button>
49
+ *
50
+ * @example explicitly target a non-active company
51
+ * const canRead = useCan("payments", "read", { companyId: targetId });
52
+ */
53
+ declare function useCan(resource: string, action: Action, options?: CanOptions): boolean;
54
+
55
+ interface CanProps extends CanOptions {
56
+ resource: string;
57
+ action: Action;
58
+ /** Rendered when the user has the permission. */
59
+ children: ReactNode;
60
+ /**
61
+ * Rendered when the user does NOT have the permission. Defaults
62
+ * to `null` (silent hide). Pass a `<NoPermissionView />` or a
63
+ * tooltip-wrapper to surface the denial explicitly.
64
+ */
65
+ fallback?: ReactNode;
66
+ }
67
+ /**
68
+ * Subtree gate. Bails before children render so any data fetching
69
+ * inside `children` is skipped for users without permission.
70
+ */
71
+ declare function Can(props: CanProps): react_jsx_runtime.JSX.Element;
72
+
73
+ interface RequirePermissionProps extends CanOptions {
74
+ resource: string;
75
+ action: Action;
76
+ /**
77
+ * What to render while the profile is still loading. Defaults to
78
+ * `null` (no flash) — pass a spinner if your routes typically
79
+ * mount before the profile lands.
80
+ */
81
+ loadingFallback?: ReactNode;
82
+ /**
83
+ * What to render when access is denied. Defaults to a minimal
84
+ * "Sie haben keinen Zugriff" message; pass your own component to
85
+ * theme it.
86
+ */
87
+ deniedFallback?: ReactNode;
88
+ /**
89
+ * For `react-router-dom v6` route-element usage, pass an `<Outlet />`
90
+ * here — the gate resolves to either the outlet or the denied
91
+ * fallback. For component-tree usage, pass any children.
92
+ */
93
+ children?: ReactNode;
94
+ }
95
+ /**
96
+ * Route- or component-level guard. Three render branches:
97
+ *
98
+ * - profile not yet loaded → `loadingFallback`
99
+ * - permission denied → `deniedFallback`
100
+ * - permission granted → `children`
101
+ *
102
+ * Drop-in replacement for the legacy `<RequireRolesRoute>` pattern.
103
+ *
104
+ * @example
105
+ * // App.tsx route table
106
+ * <Route element={
107
+ * <RequirePermission resource="payments" action="read">
108
+ * <Outlet />
109
+ * </RequirePermission>
110
+ * }>
111
+ * <Route path="/payments" element={<PaymentsPage />} />
112
+ * </Route>
113
+ */
114
+ declare function RequirePermission(props: RequirePermissionProps): react_jsx_runtime.JSX.Element;
115
+
116
+ interface ActiveCompany {
117
+ id: string | null;
118
+ membership: CompanyMembership | null;
119
+ memberships: CompanyMembership[];
120
+ setActive: (id: string | null) => void;
121
+ }
122
+ /**
123
+ * Read + switch the active company.
124
+ *
125
+ * @example
126
+ * const { id, memberships, setActive } = useActiveCompany();
127
+ *
128
+ * return (
129
+ * <select value={id ?? ""} onChange={(e) => setActive(e.target.value || null)}>
130
+ * {memberships.map((m) => (
131
+ * <option key={m.company_id} value={m.company_id}>{m.company_name}</option>
132
+ * ))}
133
+ * </select>
134
+ * );
135
+ */
136
+ declare function useActiveCompany(): ActiveCompany;
137
+
138
+ /**
139
+ * Reads the merged `frontend_config` for the user. Sources in
140
+ * priority order: active company's membership > system roles. Use
141
+ * this to drive sidebar items, dashboard defaults, and any other
142
+ * "what should this role see" UX without hardcoded role checks.
143
+ *
144
+ * The shape is intentionally `Record<string, unknown>` — your host
145
+ * project owns the schema. Document your keys (e.g. `sidebar`,
146
+ * `default_dashboard`) once and stick to them.
147
+ */
148
+ declare function useFrontendConfig(): FrontendConfig;
149
+
150
+ /**
151
+ * Typed factory — turns a const-asserted resource registry into a
152
+ * set of hooks/components whose `resource` arg is constrained to
153
+ * the registered names. Typos become TypeScript errors instead of
154
+ * silent runtime `false`.
155
+ *
156
+ * @example
157
+ * // src/auth/resources.ts
158
+ * import { defineAuthRbac } from "snipe-auth-rbac/react";
159
+ *
160
+ * export const RESOURCES = [
161
+ * { resource: "properties", scope: "company", label: "Liegenschaften", group: "Stammdaten" },
162
+ * { resource: "payments", scope: "company", label: "Zahlungen", group: "Finanzen" },
163
+ * { resource: "system_audit", scope: "system", label: "Audit-Log", group: "Plattform" },
164
+ * ] as const;
165
+ *
166
+ * export const { useCan, Can, RequirePermission } = defineAuthRbac(RESOURCES);
167
+ *
168
+ * // ----- elsewhere -----
169
+ * useCan("properties", "update"); // ✓
170
+ * useCan("paymetns", "update"); // ✗ TS error: not assignable to type "properties" | "payments" | "system_audit"
171
+ */
172
+
173
+ /**
174
+ * Drop-in replacement signatures for the three guards, with a
175
+ * narrowed `resource` arg.
176
+ */
177
+ interface TypedGuards<R extends string> {
178
+ useCan: (resource: R, action: Action, options?: {
179
+ companyId?: string | null;
180
+ }) => boolean;
181
+ Can: ComponentType<{
182
+ resource: R;
183
+ action: Action;
184
+ companyId?: string | null;
185
+ children: ReactNode;
186
+ fallback?: ReactNode;
187
+ }>;
188
+ RequirePermission: ComponentType<{
189
+ resource: R;
190
+ action: Action;
191
+ companyId?: string | null;
192
+ loadingFallback?: ReactNode;
193
+ deniedFallback?: ReactNode;
194
+ children?: ReactNode;
195
+ }>;
196
+ /** The const-asserted registry, re-exported so call-sites can iterate. */
197
+ resources: ResourceRegistry;
198
+ /** All registered resource names as a union — handy for typing
199
+ * application-side data structures. */
200
+ resourceNames: ReadonlyArray<R>;
201
+ }
202
+
203
+ /**
204
+ * React + Next.js entry. Import this in browser code:
205
+ *
206
+ * import { AuthRbacProvider, useCan } from "snipe-auth-rbac/react";
207
+ *
208
+ * The non-React entry (`snipe-auth-rbac`) re-exports types and the
209
+ * pure resolver, suitable for Node, edge workers, and tests.
210
+ */
211
+
212
+ /**
213
+ * Typed factory — pass a const-asserted resource registry and get
214
+ * back guards whose `resource` arg is constrained to the registered
215
+ * names. Recommended at the top of every host project.
216
+ *
217
+ * See ../define.ts for the full doc + example.
218
+ */
219
+ declare function defineAuthRbac<const Reg extends ReadonlyArray<ResourceDescriptor>>(resources: Reg): TypedGuards<Reg[number]["resource"]>;
220
+
221
+ export { Action, type ActiveCompany, AuthRbacFetcher, AuthRbacProvider, type AuthRbacProviderProps, Can, type CanProps, CompanyMembership, FrontendConfig, RequirePermission, type RequirePermissionProps, ResourceDescriptor, ResourceRegistry, type TypedGuards, UserProfile, defineAuthRbac, useActiveCompany, useAuthRbac, useCan, useFrontendConfig };
@@ -0,0 +1,227 @@
1
+ import {
2
+ createHttpFetcher,
3
+ createSupabaseFetcher
4
+ } from "../chunk-BRCJUCDG.js";
5
+ import {
6
+ buildPermissionResolver
7
+ } from "../chunk-4WTV6J44.js";
8
+
9
+ // src/react/AuthRbacProvider.tsx
10
+ import {
11
+ createContext,
12
+ useCallback,
13
+ useContext,
14
+ useEffect,
15
+ useMemo,
16
+ useState
17
+ } from "react";
18
+ import { jsx } from "react/jsx-runtime";
19
+ var AuthRbacContext = createContext(null);
20
+ var STORAGE_KEY = "auth-rbac:active-company";
21
+ var defaultPersist = (id) => {
22
+ if (typeof window === "undefined") {
23
+ return;
24
+ }
25
+ try {
26
+ if (id == null) {
27
+ window.localStorage.removeItem(STORAGE_KEY);
28
+ } else {
29
+ window.localStorage.setItem(STORAGE_KEY, id);
30
+ }
31
+ } catch {
32
+ }
33
+ };
34
+ var readPersisted = () => {
35
+ if (typeof window === "undefined") {
36
+ return null;
37
+ }
38
+ try {
39
+ return window.localStorage.getItem(STORAGE_KEY);
40
+ } catch {
41
+ return null;
42
+ }
43
+ };
44
+ function AuthRbacProvider(props) {
45
+ const { fetcher, resources, initialCompanyId, persistActiveCompany } = props;
46
+ const [profile, setProfile] = useState(null);
47
+ const [loading, setLoading] = useState(true);
48
+ const [error, setError] = useState(null);
49
+ const [activeCompanyId, setActiveCompanyState] = useState(
50
+ initialCompanyId ?? readPersisted()
51
+ );
52
+ const persist = useMemo(() => {
53
+ if (persistActiveCompany === false) {
54
+ return () => {
55
+ };
56
+ }
57
+ return persistActiveCompany ?? defaultPersist;
58
+ }, [persistActiveCompany]);
59
+ const setActiveCompany = useCallback(
60
+ (id) => {
61
+ setActiveCompanyState(id);
62
+ persist(id);
63
+ },
64
+ [persist]
65
+ );
66
+ const refresh = useCallback(async () => {
67
+ setLoading(true);
68
+ setError(null);
69
+ try {
70
+ const next = await fetcher.fetchProfile();
71
+ setProfile(next);
72
+ const stillMember = activeCompanyId != null && next.memberships.some((m) => m.company_id === activeCompanyId);
73
+ if (!stillMember) {
74
+ const fallback = next.memberships[0]?.company_id ?? null;
75
+ setActiveCompanyState(fallback);
76
+ persist(fallback);
77
+ }
78
+ } catch (e) {
79
+ setError(e instanceof Error ? e : new Error(String(e)));
80
+ } finally {
81
+ setLoading(false);
82
+ }
83
+ }, [fetcher]);
84
+ useEffect(() => {
85
+ void refresh();
86
+ }, [refresh]);
87
+ const resolver = useMemo(() => {
88
+ if (profile == null) {
89
+ return {
90
+ can: () => false,
91
+ activePermissions: () => ({}),
92
+ systemPermissions: () => ({})
93
+ };
94
+ }
95
+ return buildPermissionResolver(resources, profile, activeCompanyId);
96
+ }, [profile, resources, activeCompanyId]);
97
+ const value = useMemo(
98
+ () => ({
99
+ profile,
100
+ loading,
101
+ error,
102
+ resources,
103
+ activeCompanyId,
104
+ setActiveCompany,
105
+ refresh,
106
+ resolver
107
+ }),
108
+ [
109
+ profile,
110
+ loading,
111
+ error,
112
+ resources,
113
+ activeCompanyId,
114
+ setActiveCompany,
115
+ refresh,
116
+ resolver
117
+ ]
118
+ );
119
+ return /* @__PURE__ */ jsx(AuthRbacContext.Provider, { value, children: props.children });
120
+ }
121
+ function useAuthRbac() {
122
+ const ctx = useContext(AuthRbacContext);
123
+ if (!ctx) {
124
+ throw new Error(
125
+ "useAuthRbac must be used within an <AuthRbacProvider> \u2014 wrap your app at the root."
126
+ );
127
+ }
128
+ return ctx;
129
+ }
130
+
131
+ // src/react/useCan.ts
132
+ function useCan(resource, action, options) {
133
+ const { resolver } = useAuthRbac();
134
+ return resolver.can(resource, action, options);
135
+ }
136
+
137
+ // src/react/Can.tsx
138
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
139
+ function Can(props) {
140
+ const { resource, action, companyId, children, fallback = null } = props;
141
+ const allowed = useCan(resource, action, { companyId });
142
+ return /* @__PURE__ */ jsx2(Fragment, { children: allowed ? children : fallback });
143
+ }
144
+
145
+ // src/react/RequirePermission.tsx
146
+ import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
147
+ function RequirePermission(props) {
148
+ const {
149
+ resource,
150
+ action,
151
+ companyId,
152
+ loadingFallback = null,
153
+ deniedFallback = /* @__PURE__ */ jsx3("div", { role: "alert", style: { padding: 24 }, children: /* @__PURE__ */ jsx3("strong", { children: "Sie haben keinen Zugriff." }) }),
154
+ children = null
155
+ } = props;
156
+ const { profile, loading } = useAuthRbac();
157
+ const allowed = useCan(resource, action, { companyId });
158
+ if (loading || profile == null) {
159
+ return /* @__PURE__ */ jsx3(Fragment2, { children: loadingFallback });
160
+ }
161
+ if (!allowed) {
162
+ return /* @__PURE__ */ jsx3(Fragment2, { children: deniedFallback });
163
+ }
164
+ return /* @__PURE__ */ jsx3(Fragment2, { children });
165
+ }
166
+
167
+ // src/react/useActiveCompany.ts
168
+ import { useMemo as useMemo2 } from "react";
169
+ function useActiveCompany() {
170
+ const { profile, activeCompanyId, setActiveCompany } = useAuthRbac();
171
+ return useMemo2(() => {
172
+ const memberships = profile?.memberships ?? [];
173
+ const membership = memberships.find((m) => m.company_id === activeCompanyId) ?? null;
174
+ return {
175
+ id: activeCompanyId,
176
+ membership,
177
+ memberships,
178
+ setActive: setActiveCompany
179
+ };
180
+ }, [profile, activeCompanyId, setActiveCompany]);
181
+ }
182
+
183
+ // src/react/useFrontendConfig.ts
184
+ import { useMemo as useMemo3 } from "react";
185
+ function useFrontendConfig() {
186
+ const { profile, activeCompanyId } = useAuthRbac();
187
+ return useMemo3(() => {
188
+ if (!profile) {
189
+ return {};
190
+ }
191
+ const membershipConfig = profile.memberships.find((m) => m.company_id === activeCompanyId)?.frontend_config ?? {};
192
+ return { ...profile.system_frontend_config, ...membershipConfig };
193
+ }, [profile, activeCompanyId]);
194
+ }
195
+
196
+ // src/define.ts
197
+ function defineAuthRbac(resources, runtime) {
198
+ return {
199
+ useCan: runtime.useCan,
200
+ Can: runtime.Can,
201
+ RequirePermission: runtime.RequirePermission,
202
+ resources,
203
+ resourceNames: resources.map((r) => r.resource)
204
+ };
205
+ }
206
+
207
+ // src/react/index.ts
208
+ function defineAuthRbac2(resources) {
209
+ return defineAuthRbac(resources, {
210
+ useCan,
211
+ Can,
212
+ RequirePermission
213
+ });
214
+ }
215
+ export {
216
+ AuthRbacProvider,
217
+ Can,
218
+ RequirePermission,
219
+ createHttpFetcher,
220
+ createSupabaseFetcher,
221
+ defineAuthRbac2 as defineAuthRbac,
222
+ useActiveCompany,
223
+ useAuthRbac,
224
+ useCan,
225
+ useFrontendConfig
226
+ };
227
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/AuthRbacProvider.tsx","../../src/react/useCan.ts","../../src/react/Can.tsx","../../src/react/RequirePermission.tsx","../../src/react/useActiveCompany.ts","../../src/react/useFrontendConfig.ts","../../src/define.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 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 { 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 * Typed factory — turns a const-asserted resource registry into a\n * set of hooks/components whose `resource` arg is constrained to\n * the registered names. Typos become TypeScript errors instead of\n * silent runtime `false`.\n *\n * @example\n * // src/auth/resources.ts\n * import { defineAuthRbac } from \"snipe-auth-rbac/react\";\n *\n * export const RESOURCES = [\n * { resource: \"properties\", scope: \"company\", label: \"Liegenschaften\", group: \"Stammdaten\" },\n * { resource: \"payments\", scope: \"company\", label: \"Zahlungen\", group: \"Finanzen\" },\n * { resource: \"system_audit\", scope: \"system\", label: \"Audit-Log\", group: \"Plattform\" },\n * ] as const;\n *\n * export const { useCan, Can, RequirePermission } = defineAuthRbac(RESOURCES);\n *\n * // ----- elsewhere -----\n * useCan(\"properties\", \"update\"); // ✓\n * useCan(\"paymetns\", \"update\"); // ✗ TS error: not assignable to type \"properties\" | \"payments\" | \"system_audit\"\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport type {\n Action,\n ResourceDescriptor,\n ResourceRegistry,\n} from \"./types.js\";\n\n/**\n * Drop-in replacement signatures for the three guards, with a\n * narrowed `resource` arg.\n */\nexport interface TypedGuards<R extends string> {\n useCan: (\n resource: R,\n action: Action,\n options?: { companyId?: string | null },\n ) => boolean;\n Can: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n children: ReactNode;\n fallback?: ReactNode;\n }>;\n RequirePermission: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n loadingFallback?: ReactNode;\n deniedFallback?: ReactNode;\n children?: ReactNode;\n }>;\n /** The const-asserted registry, re-exported so call-sites can iterate. */\n resources: ResourceRegistry;\n /** All registered resource names as a union — handy for typing\n * application-side data structures. */\n resourceNames: ReadonlyArray<R>;\n}\n\n/**\n * Factory. Run once at module-init time in your host project; the\n * returned hooks/components are referentially stable.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(\n resources: Reg,\n // The runtime guards live in the React entry; we accept them\n // here as plain refs so this module stays React-free at the type\n // level. The `react` entry calls this factory passing its own\n // exports so adopters never see the wiring.\n runtime: {\n useCan: TypedGuards<string>[\"useCan\"];\n Can: TypedGuards<string>[\"Can\"];\n RequirePermission: TypedGuards<string>[\"RequirePermission\"];\n },\n): TypedGuards<Reg[number][\"resource\"]> {\n type R = Reg[number][\"resource\"];\n return {\n useCan: runtime.useCan as TypedGuards<R>[\"useCan\"],\n Can: runtime.Can as TypedGuards<R>[\"Can\"],\n RequirePermission: runtime.RequirePermission as TypedGuards<R>[\"RequirePermission\"],\n resources,\n resourceNames: resources.map((r) => r.resource) as ReadonlyArray<R>,\n };\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 { 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 FrontendConfig,\n PermissionGrid,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n RoleSummary,\n UserProfile,\n} from \"../types.js\";\n\nexport {\n createSupabaseFetcher,\n createHttpFetcher,\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\";\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 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"]}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Core types — used by both the transport-agnostic client and the
3
+ * React layer. Adopters that don't use React only need to depend on
4
+ * the `index` entry; the `react` entry's types extend these.
5
+ */
6
+ type Action = "read" | "write" | "update" | "delete";
7
+ type ResourceScope = "system" | "company";
8
+ interface ResourceDescriptor {
9
+ resource: string;
10
+ scope: ResourceScope;
11
+ label: string;
12
+ description?: string;
13
+ group?: string;
14
+ }
15
+ interface RoleSummary {
16
+ id: string;
17
+ name: string;
18
+ is_system?: boolean;
19
+ is_super?: boolean;
20
+ frontend_config?: FrontendConfig;
21
+ }
22
+ /**
23
+ * Free-form per-role config that the host project can interpret as
24
+ * it sees fit. A typical shape includes `sidebar` (list of section
25
+ * keys), `default_dashboard` (string), `home_route` (string).
26
+ */
27
+ type FrontendConfig = Record<string, unknown>;
28
+ interface PermissionGrid {
29
+ read: boolean;
30
+ write: boolean;
31
+ update: boolean;
32
+ delete: boolean;
33
+ }
34
+ type PermissionMap = Record<string, PermissionGrid>;
35
+ interface CompanyMembership {
36
+ company_id: string;
37
+ company_name: string;
38
+ company_slug?: string | null;
39
+ roles: RoleSummary[];
40
+ permissions: PermissionMap;
41
+ /** Merged frontend_config across all roles the user holds in this company. */
42
+ frontend_config: FrontendConfig;
43
+ }
44
+ interface UserProfile {
45
+ user_id: string;
46
+ is_super_admin: boolean;
47
+ system_roles: RoleSummary[];
48
+ system_permissions: PermissionMap;
49
+ /** Merged frontend_config across all of the user's system roles. */
50
+ system_frontend_config: FrontendConfig;
51
+ memberships: CompanyMembership[];
52
+ }
53
+ /**
54
+ * Adopter-supplied transport. Two flavours are supported out of the
55
+ * box:
56
+ *
57
+ * 1. Pass a Supabase client + a JWT-derived `user_id` and the
58
+ * library calls the package's SQL RPC `auth_rbac_user_profile`.
59
+ * 2. Pass a plain `fetcher` (anything that resolves a `UserProfile`)
60
+ * and the library calls that. Use this when your backend serves
61
+ * a custom `/api/users/me/profile` endpoint or when you don't
62
+ * run Supabase at all.
63
+ */
64
+ interface AuthRbacFetcher {
65
+ fetchProfile(): Promise<UserProfile>;
66
+ }
67
+ type ResourceRegistry = ReadonlyArray<ResourceDescriptor>;
68
+
69
+ export type { Action as A, CompanyMembership as C, FrontendConfig as F, PermissionGrid as P, ResourceScope as R, UserProfile as U, ResourceDescriptor as a, AuthRbacFetcher as b, ResourceRegistry as c, PermissionMap as d, RoleSummary as e };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Core types — used by both the transport-agnostic client and the
3
+ * React layer. Adopters that don't use React only need to depend on
4
+ * the `index` entry; the `react` entry's types extend these.
5
+ */
6
+ type Action = "read" | "write" | "update" | "delete";
7
+ type ResourceScope = "system" | "company";
8
+ interface ResourceDescriptor {
9
+ resource: string;
10
+ scope: ResourceScope;
11
+ label: string;
12
+ description?: string;
13
+ group?: string;
14
+ }
15
+ interface RoleSummary {
16
+ id: string;
17
+ name: string;
18
+ is_system?: boolean;
19
+ is_super?: boolean;
20
+ frontend_config?: FrontendConfig;
21
+ }
22
+ /**
23
+ * Free-form per-role config that the host project can interpret as
24
+ * it sees fit. A typical shape includes `sidebar` (list of section
25
+ * keys), `default_dashboard` (string), `home_route` (string).
26
+ */
27
+ type FrontendConfig = Record<string, unknown>;
28
+ interface PermissionGrid {
29
+ read: boolean;
30
+ write: boolean;
31
+ update: boolean;
32
+ delete: boolean;
33
+ }
34
+ type PermissionMap = Record<string, PermissionGrid>;
35
+ interface CompanyMembership {
36
+ company_id: string;
37
+ company_name: string;
38
+ company_slug?: string | null;
39
+ roles: RoleSummary[];
40
+ permissions: PermissionMap;
41
+ /** Merged frontend_config across all roles the user holds in this company. */
42
+ frontend_config: FrontendConfig;
43
+ }
44
+ interface UserProfile {
45
+ user_id: string;
46
+ is_super_admin: boolean;
47
+ system_roles: RoleSummary[];
48
+ system_permissions: PermissionMap;
49
+ /** Merged frontend_config across all of the user's system roles. */
50
+ system_frontend_config: FrontendConfig;
51
+ memberships: CompanyMembership[];
52
+ }
53
+ /**
54
+ * Adopter-supplied transport. Two flavours are supported out of the
55
+ * box:
56
+ *
57
+ * 1. Pass a Supabase client + a JWT-derived `user_id` and the
58
+ * library calls the package's SQL RPC `auth_rbac_user_profile`.
59
+ * 2. Pass a plain `fetcher` (anything that resolves a `UserProfile`)
60
+ * and the library calls that. Use this when your backend serves
61
+ * a custom `/api/users/me/profile` endpoint or when you don't
62
+ * run Supabase at all.
63
+ */
64
+ interface AuthRbacFetcher {
65
+ fetchProfile(): Promise<UserProfile>;
66
+ }
67
+ type ResourceRegistry = ReadonlyArray<ResourceDescriptor>;
68
+
69
+ export type { Action as A, CompanyMembership as C, FrontendConfig as F, PermissionGrid as P, ResourceScope as R, UserProfile as U, ResourceDescriptor as a, AuthRbacFetcher as b, ResourceRegistry as c, PermissionMap as d, RoleSummary as e };