@pol-studios/db 1.0.37 → 1.0.39
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/UserMetadataContext-yLZQu24J.d.ts +33 -0
- package/dist/auth/context.d.ts +5 -59
- package/dist/auth/context.js +2 -26
- package/dist/auth/hooks.d.ts +3 -107
- package/dist/auth/hooks.js +3 -9
- package/dist/auth/index.d.ts +5 -5
- package/dist/auth/index.js +10 -40
- package/dist/{chunk-7P2LGZYQ.js → chunk-3UW5K5PL.js} +3 -3
- package/dist/{chunk-UBHORKBS.js → chunk-5SJ5O2NQ.js} +75 -2
- package/dist/chunk-5SJ5O2NQ.js.map +1 -0
- package/dist/{chunk-X747EEWD.js → chunk-AQ5JJKIN.js} +4 -4
- package/dist/{chunk-NP34C3O3.js → chunk-D2F3APBF.js} +30 -103
- package/dist/chunk-D2F3APBF.js.map +1 -0
- package/dist/{chunk-7NFMEDJW.js → chunk-D7UZEYKO.js} +9 -42
- package/dist/chunk-D7UZEYKO.js.map +1 -0
- package/dist/{chunk-YQUNORJD.js → chunk-MZLEDWJF.js} +310 -392
- package/dist/chunk-MZLEDWJF.js.map +1 -0
- package/dist/chunk-NSIAAYW3.js +1 -0
- package/dist/{chunk-U4BZKCBH.js → chunk-Z6ZHXO2R.js} +4 -4
- package/dist/hooks/index.js +4 -4
- package/dist/index.js +10 -11
- package/dist/index.native.js +10 -11
- package/dist/index.web.js +9 -10
- package/dist/index.web.js.map +1 -1
- package/dist/with-auth/index.js +6 -7
- package/dist/with-auth/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/UserMetadataContext-QLIv-mfF.d.ts +0 -171
- package/dist/chunk-5HJLTYRA.js +0 -355
- package/dist/chunk-5HJLTYRA.js.map +0 -1
- package/dist/chunk-6KN7KLEG.js +0 -1
- package/dist/chunk-7NFMEDJW.js.map +0 -1
- package/dist/chunk-NP34C3O3.js.map +0 -1
- package/dist/chunk-UBHORKBS.js.map +0 -1
- package/dist/chunk-YQUNORJD.js.map +0 -1
- /package/dist/{chunk-7P2LGZYQ.js.map → chunk-3UW5K5PL.js.map} +0 -0
- /package/dist/{chunk-X747EEWD.js.map → chunk-AQ5JJKIN.js.map} +0 -0
- /package/dist/{chunk-6KN7KLEG.js.map → chunk-NSIAAYW3.js.map} +0 -0
- /package/dist/{chunk-U4BZKCBH.js.map → chunk-Z6ZHXO2R.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
useDbQuery
|
|
3
|
-
|
|
2
|
+
useDbQuery,
|
|
3
|
+
useDbQueryById
|
|
4
|
+
} from "./chunk-5SJ5O2NQ.js";
|
|
4
5
|
import {
|
|
5
6
|
buildNormalizedQuery,
|
|
6
7
|
encode,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
useUpsertItem
|
|
9
10
|
} from "./chunk-YUX6RGLZ.js";
|
|
10
11
|
import {
|
|
11
|
-
typedSupabase,
|
|
12
12
|
useSupabase
|
|
13
13
|
} from "./chunk-DMVUEJG2.js";
|
|
14
14
|
|
|
@@ -72,9 +72,292 @@ function useDbQuery2(query, config) {
|
|
|
72
72
|
import { createContext } from "react";
|
|
73
73
|
var setupAuthContext = createContext({});
|
|
74
74
|
|
|
75
|
-
// src/auth/context/
|
|
76
|
-
import {
|
|
75
|
+
// src/auth/context/AuthProvider.tsx
|
|
76
|
+
import { useCallback, useEffect, useMemo as useMemo2, useRef as useRef2, useState } from "react";
|
|
77
|
+
import { isUsable, newUuid } from "@pol-studios/utils";
|
|
77
78
|
import { jsx } from "react/jsx-runtime";
|
|
79
|
+
var SESSION_FETCH_TIMEOUT_MS = 3e3;
|
|
80
|
+
function getPermissionLevel(action) {
|
|
81
|
+
switch (action.toLowerCase()) {
|
|
82
|
+
case "view":
|
|
83
|
+
case "read":
|
|
84
|
+
return 1;
|
|
85
|
+
case "edit":
|
|
86
|
+
case "write":
|
|
87
|
+
return 2;
|
|
88
|
+
case "admin":
|
|
89
|
+
case "delete":
|
|
90
|
+
case "share":
|
|
91
|
+
return 3;
|
|
92
|
+
default:
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function isNotExpired(expiresAt) {
|
|
97
|
+
return !expiresAt || new Date(expiresAt) > /* @__PURE__ */ new Date();
|
|
98
|
+
}
|
|
99
|
+
function matchesAccessPattern(pattern, requestedKey) {
|
|
100
|
+
if (pattern === requestedKey) return true;
|
|
101
|
+
if (pattern === "*:*:*") return true;
|
|
102
|
+
const patternParts = pattern.split(":");
|
|
103
|
+
const keyParts = requestedKey.split(":");
|
|
104
|
+
if (patternParts.length !== keyParts.length) return false;
|
|
105
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
106
|
+
if (patternParts[i] === "*") continue;
|
|
107
|
+
if (patternParts[i] !== keyParts[i]) return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
function AuthProvider({
|
|
112
|
+
children
|
|
113
|
+
}) {
|
|
114
|
+
const supabase = useSupabase();
|
|
115
|
+
const [currentUser, setCurrentUser] = useState(void 0);
|
|
116
|
+
const [userNeedsChange, setUserNeedsChange] = useState(true);
|
|
117
|
+
const [onSignOutCallbacks, setOnSignOutCallbacks] = useState(/* @__PURE__ */ new Map());
|
|
118
|
+
async function registerAsync(register) {
|
|
119
|
+
const response = await supabase.auth.signUp(register);
|
|
120
|
+
if (response.data.user) {
|
|
121
|
+
setCurrentUser((prev) => prev?.id === response.data.user?.id ? prev : response.data.user);
|
|
122
|
+
}
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
async function signInAsync(username, password) {
|
|
126
|
+
const response_0 = await supabase.auth.signInWithPassword({
|
|
127
|
+
email: username,
|
|
128
|
+
password
|
|
129
|
+
});
|
|
130
|
+
if (response_0.data.user) {
|
|
131
|
+
setCurrentUser((prev_0) => prev_0?.id === response_0.data.user?.id ? prev_0 : response_0.data.user);
|
|
132
|
+
}
|
|
133
|
+
return response_0;
|
|
134
|
+
}
|
|
135
|
+
const signOutAsync = useCallback(async () => {
|
|
136
|
+
const response_1 = await supabase.auth.signOut();
|
|
137
|
+
if (!response_1.error) {
|
|
138
|
+
Array.from(onSignOutCallbacks.values()).forEach((cb) => cb());
|
|
139
|
+
}
|
|
140
|
+
return response_1;
|
|
141
|
+
}, [supabase.auth, onSignOutCallbacks]);
|
|
142
|
+
function onSignOut(action) {
|
|
143
|
+
const id = newUuid();
|
|
144
|
+
setOnSignOutCallbacks((x) => new Map(x).set(id, action));
|
|
145
|
+
return id;
|
|
146
|
+
}
|
|
147
|
+
function removeOnSignOut(id_0) {
|
|
148
|
+
setOnSignOutCallbacks((x_0) => {
|
|
149
|
+
const map = new Map(x_0);
|
|
150
|
+
map.delete(id_0);
|
|
151
|
+
return map;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function refreshAsync() {
|
|
155
|
+
}
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
const {
|
|
158
|
+
data: {
|
|
159
|
+
subscription
|
|
160
|
+
}
|
|
161
|
+
} = supabase.auth.onAuthStateChange((event) => {
|
|
162
|
+
if (event === "SIGNED_IN" || event === "SIGNED_OUT") {
|
|
163
|
+
setUserNeedsChange(true);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return () => subscription.unsubscribe();
|
|
167
|
+
}, [supabase.auth]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (!userNeedsChange) return;
|
|
170
|
+
let cancelled = false;
|
|
171
|
+
async function fetchSessionWithTimeout() {
|
|
172
|
+
try {
|
|
173
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
reject(new Error(`Session fetch timed out after ${SESSION_FETCH_TIMEOUT_MS}ms`));
|
|
176
|
+
}, SESSION_FETCH_TIMEOUT_MS);
|
|
177
|
+
});
|
|
178
|
+
const result = await Promise.race([supabase.auth.getSession(), timeoutPromise]);
|
|
179
|
+
if (cancelled) return;
|
|
180
|
+
const newUser = result?.data?.session?.user ?? null;
|
|
181
|
+
setCurrentUser((prev_2) => {
|
|
182
|
+
if (newUser === null) return null;
|
|
183
|
+
if (prev_2?.id === newUser?.id) return prev_2;
|
|
184
|
+
return newUser;
|
|
185
|
+
});
|
|
186
|
+
setUserNeedsChange(false);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (cancelled) return;
|
|
189
|
+
console.error("Failed to get session (timeout or error):", error);
|
|
190
|
+
setCurrentUser((prev_1) => prev_1 === null ? prev_1 : null);
|
|
191
|
+
setUserNeedsChange(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
fetchSessionWithTimeout();
|
|
195
|
+
return () => {
|
|
196
|
+
cancelled = true;
|
|
197
|
+
};
|
|
198
|
+
}, [userNeedsChange, supabase.auth]);
|
|
199
|
+
const isUserReady = isUsable(currentUser);
|
|
200
|
+
const {
|
|
201
|
+
data: profile,
|
|
202
|
+
isLoading: profileLoading
|
|
203
|
+
} = useDbQueryById("core.Profile", currentUser?.id ?? "", {
|
|
204
|
+
enabled: isUserReady
|
|
205
|
+
});
|
|
206
|
+
const {
|
|
207
|
+
data: directAccess,
|
|
208
|
+
isLoading: directAccessLoading
|
|
209
|
+
} = useDbQuery("core.UserAccess", {
|
|
210
|
+
where: {
|
|
211
|
+
userId: currentUser?.id
|
|
212
|
+
},
|
|
213
|
+
enabled: isUserReady,
|
|
214
|
+
realtime: true
|
|
215
|
+
});
|
|
216
|
+
const {
|
|
217
|
+
data: userGroups,
|
|
218
|
+
isLoading: userGroupsLoading
|
|
219
|
+
} = useDbQuery("core.UserGroup", {
|
|
220
|
+
where: {
|
|
221
|
+
userId: currentUser?.id
|
|
222
|
+
},
|
|
223
|
+
enabled: isUserReady,
|
|
224
|
+
realtime: true
|
|
225
|
+
});
|
|
226
|
+
const {
|
|
227
|
+
data: groups,
|
|
228
|
+
isLoading: groupsLoading
|
|
229
|
+
} = useDbQuery("core.Group", {
|
|
230
|
+
where: {
|
|
231
|
+
isActive: 1
|
|
232
|
+
},
|
|
233
|
+
enabled: isUserReady,
|
|
234
|
+
realtime: true
|
|
235
|
+
});
|
|
236
|
+
const groupIds = useMemo2(() => userGroups?.map((ug) => ug.groupId) ?? [], [userGroups]);
|
|
237
|
+
const groupsMap = useMemo2(() => new Map(groups?.map((g) => [g.id, g]) ?? []), [groups]);
|
|
238
|
+
const {
|
|
239
|
+
data: groupAccess,
|
|
240
|
+
isLoading: groupAccessLoading
|
|
241
|
+
} = useDbQuery("core.GroupAccessKey", {
|
|
242
|
+
where: groupIds.length > 0 ? {
|
|
243
|
+
groupId: {
|
|
244
|
+
in: groupIds
|
|
245
|
+
}
|
|
246
|
+
} : void 0,
|
|
247
|
+
enabled: groupIds.length > 0,
|
|
248
|
+
realtime: true
|
|
249
|
+
});
|
|
250
|
+
const prevProfileStatusRef = useRef2(void 0);
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
const currentStatus = profile?.status;
|
|
253
|
+
const prevStatus = prevProfileStatusRef.current;
|
|
254
|
+
if (prevStatus === "active" && (currentStatus === "archived" || currentStatus === "suspended")) {
|
|
255
|
+
signOutAsync();
|
|
256
|
+
}
|
|
257
|
+
prevProfileStatusRef.current = currentStatus;
|
|
258
|
+
}, [profile?.status, signOutAsync]);
|
|
259
|
+
const allAccessKeys = useMemo2(() => {
|
|
260
|
+
const keys = [];
|
|
261
|
+
directAccess?.forEach((a) => {
|
|
262
|
+
if (isNotExpired(a.expiresAt)) {
|
|
263
|
+
keys.push({
|
|
264
|
+
accessKey: a.accessKey,
|
|
265
|
+
effect: a.effect ?? "allow",
|
|
266
|
+
source: "direct",
|
|
267
|
+
expiresAt: a.expiresAt
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
const activeGroupIds = new Set(userGroups?.filter((ug_0) => {
|
|
272
|
+
const group = groupsMap.get(ug_0.groupId);
|
|
273
|
+
return group?.isActive === 1 && isNotExpired(ug_0.expiresAt);
|
|
274
|
+
}).map((ug_1) => ug_1.groupId) ?? []);
|
|
275
|
+
groupAccess?.forEach((ga) => {
|
|
276
|
+
if (activeGroupIds.has(ga.groupId) && isNotExpired(ga.expiresAt)) {
|
|
277
|
+
keys.push({
|
|
278
|
+
accessKey: ga.accessKey,
|
|
279
|
+
effect: ga.effect ?? "allow",
|
|
280
|
+
source: "group",
|
|
281
|
+
expiresAt: ga.expiresAt
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
return keys;
|
|
286
|
+
}, [directAccess, userGroups, groupsMap, groupAccess]);
|
|
287
|
+
const combinedAccess = useMemo2(() => {
|
|
288
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
289
|
+
for (const item of allAccessKeys) {
|
|
290
|
+
if (item.accessKey && item.effect === "allow") {
|
|
291
|
+
uniqueKeys.add(item.accessKey);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return Array.from(uniqueKeys);
|
|
295
|
+
}, [allAccessKeys]);
|
|
296
|
+
const effectivePermissions = useMemo2(() => {
|
|
297
|
+
const permissions = [];
|
|
298
|
+
for (const item_0 of allAccessKeys) {
|
|
299
|
+
if (item_0.effect !== "allow") continue;
|
|
300
|
+
const parts = item_0.accessKey.split(":");
|
|
301
|
+
if (parts.length === 3) {
|
|
302
|
+
const [resourceType, resourceId, permission] = parts;
|
|
303
|
+
permissions.push({
|
|
304
|
+
resourceType,
|
|
305
|
+
resourceId,
|
|
306
|
+
permission,
|
|
307
|
+
permissionLevel: getPermissionLevel(permission),
|
|
308
|
+
source: item_0.source,
|
|
309
|
+
inheritedFrom: null,
|
|
310
|
+
expiresAt: item_0.expiresAt ?? null
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return permissions;
|
|
315
|
+
}, [allAccessKeys]);
|
|
316
|
+
const profileStatus = profile?.status;
|
|
317
|
+
const isArchived = profileStatus === "archived";
|
|
318
|
+
const isSuspended = profileStatus === "suspended";
|
|
319
|
+
const hasAccess = useCallback((key) => {
|
|
320
|
+
if (isArchived || isSuspended) return false;
|
|
321
|
+
if (!isUsable(combinedAccess)) return false;
|
|
322
|
+
if (combinedAccess.includes("*:*:*")) return true;
|
|
323
|
+
if (combinedAccess.includes(key)) return true;
|
|
324
|
+
if (!isUsable(key)) return true;
|
|
325
|
+
for (const pattern of combinedAccess) {
|
|
326
|
+
if (matchesAccessPattern(pattern, key)) return true;
|
|
327
|
+
}
|
|
328
|
+
const parts_0 = key.split(":");
|
|
329
|
+
if (parts_0.length === 3) {
|
|
330
|
+
const [type, id_1, action_0] = parts_0;
|
|
331
|
+
const requiredLevel = getPermissionLevel(action_0);
|
|
332
|
+
const hasPermission = effectivePermissions.some((p) => p.resourceType === type && p.resourceId === id_1 && p.permissionLevel >= requiredLevel);
|
|
333
|
+
if (hasPermission) return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}, [combinedAccess, effectivePermissions, isArchived, isSuspended]);
|
|
337
|
+
const isAccessLoading = directAccessLoading || userGroupsLoading || groupsLoading || groupAccessLoading;
|
|
338
|
+
const authState = useMemo2(() => ({
|
|
339
|
+
hasAccess,
|
|
340
|
+
user: currentUser,
|
|
341
|
+
profile,
|
|
342
|
+
access: combinedAccess,
|
|
343
|
+
effectivePermissions,
|
|
344
|
+
profileStatus,
|
|
345
|
+
isArchived,
|
|
346
|
+
isSuspended,
|
|
347
|
+
isLoading: currentUser === null ? false : profileLoading || isAccessLoading || currentUser === void 0,
|
|
348
|
+
signInAsync,
|
|
349
|
+
signOutAsync,
|
|
350
|
+
onSignOut,
|
|
351
|
+
removeOnSignOut,
|
|
352
|
+
registerAsync,
|
|
353
|
+
refreshAsync
|
|
354
|
+
}), [hasAccess, currentUser, profile, combinedAccess, effectivePermissions, profileStatus, isArchived, isSuspended, profileLoading, isAccessLoading]);
|
|
355
|
+
return /* @__PURE__ */ jsx(setupAuthContext.Provider, { value: authState, children });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/auth/context/PermissionContext.tsx
|
|
359
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
|
|
360
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
78
361
|
function getCacheKey(userId, entityType, entityId) {
|
|
79
362
|
return `${userId || "anon"}:${entityType}:${entityId}`;
|
|
80
363
|
}
|
|
@@ -189,13 +472,13 @@ function PermissionProvider({
|
|
|
189
472
|
const supabase = useSupabase();
|
|
190
473
|
const setupAuth = useContext(setupAuthContext);
|
|
191
474
|
const user = setupAuth?.user;
|
|
192
|
-
const cacheRef =
|
|
193
|
-
const pendingLookupsRef =
|
|
194
|
-
const inFlightRef =
|
|
195
|
-
const batchTimerRef =
|
|
196
|
-
const [isLoading, setIsLoading] =
|
|
197
|
-
const [, forceUpdate] =
|
|
198
|
-
const cleanupExpiredEntries =
|
|
475
|
+
const cacheRef = useRef3(/* @__PURE__ */ new Map());
|
|
476
|
+
const pendingLookupsRef = useRef3(/* @__PURE__ */ new Set());
|
|
477
|
+
const inFlightRef = useRef3(/* @__PURE__ */ new Set());
|
|
478
|
+
const batchTimerRef = useRef3(null);
|
|
479
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
480
|
+
const [, forceUpdate] = useState2(0);
|
|
481
|
+
const cleanupExpiredEntries = useCallback2(() => {
|
|
199
482
|
const now = Date.now();
|
|
200
483
|
const cache = cacheRef.current;
|
|
201
484
|
let hasExpired = false;
|
|
@@ -209,11 +492,11 @@ function PermissionProvider({
|
|
|
209
492
|
forceUpdate((prev) => prev + 1);
|
|
210
493
|
}
|
|
211
494
|
}, []);
|
|
212
|
-
|
|
495
|
+
useEffect2(() => {
|
|
213
496
|
const cleanupInterval = setInterval(cleanupExpiredEntries, 60 * 1e3);
|
|
214
497
|
return () => clearInterval(cleanupInterval);
|
|
215
498
|
}, [cleanupExpiredEntries]);
|
|
216
|
-
const executeBatchLookup =
|
|
499
|
+
const executeBatchLookup = useCallback2(async () => {
|
|
217
500
|
const pending = Array.from(pendingLookupsRef.current);
|
|
218
501
|
pendingLookupsRef.current.clear();
|
|
219
502
|
if (pending.length === 0 || !user?.id) {
|
|
@@ -273,7 +556,7 @@ function PermissionProvider({
|
|
|
273
556
|
setIsLoading(false);
|
|
274
557
|
}
|
|
275
558
|
}, [supabase, user?.id]);
|
|
276
|
-
const scheduleBatchLookup =
|
|
559
|
+
const scheduleBatchLookup = useCallback2(() => {
|
|
277
560
|
if (batchTimerRef.current) {
|
|
278
561
|
clearTimeout(batchTimerRef.current);
|
|
279
562
|
}
|
|
@@ -282,7 +565,7 @@ function PermissionProvider({
|
|
|
282
565
|
executeBatchLookup();
|
|
283
566
|
}, BATCH_DELAY_MS);
|
|
284
567
|
}, [executeBatchLookup]);
|
|
285
|
-
const getPermission =
|
|
568
|
+
const getPermission = useCallback2((entityType_0, entityId) => {
|
|
286
569
|
const key_4 = getCacheKey(user?.id, entityType_0, entityId);
|
|
287
570
|
const cache_2 = cacheRef.current;
|
|
288
571
|
const cached = cache_2.get(key_4);
|
|
@@ -296,7 +579,7 @@ function PermissionProvider({
|
|
|
296
579
|
}
|
|
297
580
|
return loadingPermission;
|
|
298
581
|
}, [scheduleBatchLookup, user?.id]);
|
|
299
|
-
const checkPermission =
|
|
582
|
+
const checkPermission = useCallback2((entityType_1, entityId_0, action) => {
|
|
300
583
|
const permission = getPermission(entityType_1, entityId_0);
|
|
301
584
|
if (permission.isLoading) {
|
|
302
585
|
return false;
|
|
@@ -318,7 +601,7 @@ function PermissionProvider({
|
|
|
318
601
|
return false;
|
|
319
602
|
}
|
|
320
603
|
}, [getPermission]);
|
|
321
|
-
const prefetchPermissions =
|
|
604
|
+
const prefetchPermissions = useCallback2(async (entities_0) => {
|
|
322
605
|
if (!user?.id || entities_0.length === 0) {
|
|
323
606
|
return;
|
|
324
607
|
}
|
|
@@ -375,12 +658,12 @@ function PermissionProvider({
|
|
|
375
658
|
setIsLoading(false);
|
|
376
659
|
}
|
|
377
660
|
}, [supabase, user?.id]);
|
|
378
|
-
const invalidatePermission =
|
|
661
|
+
const invalidatePermission = useCallback2((entityType_2, entityId_1) => {
|
|
379
662
|
const key_8 = getCacheKey(user?.id, entityType_2, entityId_1);
|
|
380
663
|
cacheRef.current.delete(key_8);
|
|
381
664
|
forceUpdate((prev_2) => prev_2 + 1);
|
|
382
665
|
}, [user?.id]);
|
|
383
|
-
const parseScopedAccessKey =
|
|
666
|
+
const parseScopedAccessKey = useCallback2((key_9) => {
|
|
384
667
|
if (!key_9 || typeof key_9 !== "string") {
|
|
385
668
|
return null;
|
|
386
669
|
}
|
|
@@ -408,7 +691,7 @@ function PermissionProvider({
|
|
|
408
691
|
entityId: entityId_2
|
|
409
692
|
};
|
|
410
693
|
}, []);
|
|
411
|
-
|
|
694
|
+
useEffect2(() => {
|
|
412
695
|
if (!user?.id) {
|
|
413
696
|
return;
|
|
414
697
|
}
|
|
@@ -436,7 +719,7 @@ function PermissionProvider({
|
|
|
436
719
|
supabase.removeChannel(channel);
|
|
437
720
|
};
|
|
438
721
|
}, [supabase, user?.id, invalidatePermission, parseScopedAccessKey]);
|
|
439
|
-
|
|
722
|
+
useEffect2(() => {
|
|
440
723
|
cacheRef.current.clear();
|
|
441
724
|
pendingLookupsRef.current.clear();
|
|
442
725
|
inFlightRef.current.clear();
|
|
@@ -446,21 +729,21 @@ function PermissionProvider({
|
|
|
446
729
|
}
|
|
447
730
|
forceUpdate((prev_3) => prev_3 + 1);
|
|
448
731
|
}, [user?.id]);
|
|
449
|
-
|
|
732
|
+
useEffect2(() => {
|
|
450
733
|
return () => {
|
|
451
734
|
if (batchTimerRef.current) {
|
|
452
735
|
clearTimeout(batchTimerRef.current);
|
|
453
736
|
}
|
|
454
737
|
};
|
|
455
738
|
}, []);
|
|
456
|
-
const value =
|
|
739
|
+
const value = useMemo3(() => ({
|
|
457
740
|
getPermission,
|
|
458
741
|
checkPermission,
|
|
459
742
|
prefetchPermissions,
|
|
460
743
|
invalidatePermission,
|
|
461
744
|
isLoading
|
|
462
745
|
}), [getPermission, checkPermission, prefetchPermissions, invalidatePermission, isLoading]);
|
|
463
|
-
return /* @__PURE__ */
|
|
746
|
+
return /* @__PURE__ */ jsx2(permissionContext.Provider, { value, children });
|
|
464
747
|
}
|
|
465
748
|
function usePermissions() {
|
|
466
749
|
const context = useContext(permissionContext);
|
|
@@ -470,371 +753,6 @@ function usePermissions() {
|
|
|
470
753
|
return context;
|
|
471
754
|
}
|
|
472
755
|
|
|
473
|
-
// src/auth/context/AuthProvider.tsx
|
|
474
|
-
import { isUsable, newUuid } from "@pol-studios/utils";
|
|
475
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
|
|
476
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
477
|
-
var SESSION_FETCH_TIMEOUT_MS = 3e3;
|
|
478
|
-
function getPermissionLevel(action) {
|
|
479
|
-
switch (action.toLowerCase()) {
|
|
480
|
-
case "view":
|
|
481
|
-
case "read":
|
|
482
|
-
return 1;
|
|
483
|
-
case "edit":
|
|
484
|
-
case "write":
|
|
485
|
-
return 2;
|
|
486
|
-
case "admin":
|
|
487
|
-
case "delete":
|
|
488
|
-
case "share":
|
|
489
|
-
return 3;
|
|
490
|
-
default:
|
|
491
|
-
return 0;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
var profileQuery = typedSupabase?.schema("core").from("Profile").select("*, UserAccess(accessKey), status").single();
|
|
495
|
-
function isNotExpired(expiresAt) {
|
|
496
|
-
return !expiresAt || new Date(expiresAt) > /* @__PURE__ */ new Date();
|
|
497
|
-
}
|
|
498
|
-
function AuthProvider({
|
|
499
|
-
children,
|
|
500
|
-
enableEntityPermissions = false
|
|
501
|
-
}) {
|
|
502
|
-
const supabase = useSupabase();
|
|
503
|
-
const [currentUser, setCurrentUser] = useState2(void 0);
|
|
504
|
-
const [userNeedsChange, setUserNeedsChange] = useState2(true);
|
|
505
|
-
const [onSignOutCallbacks, setOnSignOutCallbacks] = useState2(/* @__PURE__ */ new Map());
|
|
506
|
-
async function registerAsync(register) {
|
|
507
|
-
const response = await supabase.auth.signUp(register);
|
|
508
|
-
setCurrentUser((prev) => {
|
|
509
|
-
const newUser = response.data.user;
|
|
510
|
-
if (prev?.id === newUser?.id) {
|
|
511
|
-
return prev;
|
|
512
|
-
}
|
|
513
|
-
return newUser;
|
|
514
|
-
});
|
|
515
|
-
return response;
|
|
516
|
-
}
|
|
517
|
-
async function signInAsync(username, password) {
|
|
518
|
-
const response_0 = await supabase.auth.signInWithPassword({
|
|
519
|
-
email: username,
|
|
520
|
-
password
|
|
521
|
-
});
|
|
522
|
-
if (response_0.data) {
|
|
523
|
-
setCurrentUser((prev_0) => {
|
|
524
|
-
const newUser_0 = response_0.data.user;
|
|
525
|
-
if (prev_0?.id === newUser_0?.id) {
|
|
526
|
-
return prev_0;
|
|
527
|
-
}
|
|
528
|
-
return newUser_0;
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
return response_0;
|
|
532
|
-
}
|
|
533
|
-
async function signOutAsync() {
|
|
534
|
-
const response_1 = await supabase.auth.signOut();
|
|
535
|
-
if (isUsable(response_1.error) === false) {
|
|
536
|
-
Array.from(onSignOutCallbacks.values()).forEach((x) => {
|
|
537
|
-
x();
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
return response_1;
|
|
541
|
-
}
|
|
542
|
-
function onSignOut(action) {
|
|
543
|
-
const id = newUuid();
|
|
544
|
-
setOnSignOutCallbacks((x_0) => new Map(x_0).set(id, action));
|
|
545
|
-
return id;
|
|
546
|
-
}
|
|
547
|
-
function removeOnSignOut(id_0) {
|
|
548
|
-
setOnSignOutCallbacks((x_1) => {
|
|
549
|
-
const map = new Map(x_1);
|
|
550
|
-
map.delete(id_0);
|
|
551
|
-
return map;
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
async function refreshAsync() {
|
|
555
|
-
}
|
|
556
|
-
useEffect2(() => {
|
|
557
|
-
const request = supabase.auth.onAuthStateChange((event) => {
|
|
558
|
-
if (event === "SIGNED_IN" || event === "SIGNED_OUT") {
|
|
559
|
-
setUserNeedsChange(true);
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
return () => {
|
|
563
|
-
request.data.subscription.unsubscribe();
|
|
564
|
-
};
|
|
565
|
-
}, [supabase.auth]);
|
|
566
|
-
useEffect2(() => {
|
|
567
|
-
if (userNeedsChange === false) return;
|
|
568
|
-
let cancelled = false;
|
|
569
|
-
async function fetchSessionWithTimeout() {
|
|
570
|
-
try {
|
|
571
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
572
|
-
setTimeout(() => {
|
|
573
|
-
reject(new Error(`Session fetch timed out after ${SESSION_FETCH_TIMEOUT_MS}ms`));
|
|
574
|
-
}, SESSION_FETCH_TIMEOUT_MS);
|
|
575
|
-
});
|
|
576
|
-
const result = await Promise.race([supabase.auth.getSession(), timeoutPromise]);
|
|
577
|
-
if (cancelled) return;
|
|
578
|
-
const newUser_1 = result?.data?.session?.user ?? null;
|
|
579
|
-
setCurrentUser((prev_2) => {
|
|
580
|
-
if (newUser_1 === null) return null;
|
|
581
|
-
if (prev_2?.id === newUser_1?.id) {
|
|
582
|
-
return prev_2;
|
|
583
|
-
}
|
|
584
|
-
return newUser_1;
|
|
585
|
-
});
|
|
586
|
-
setUserNeedsChange(false);
|
|
587
|
-
} catch (error) {
|
|
588
|
-
if (cancelled) return;
|
|
589
|
-
console.error("Failed to get session (timeout or error):", error);
|
|
590
|
-
setCurrentUser((prev_1) => prev_1 === null ? prev_1 : null);
|
|
591
|
-
setUserNeedsChange(false);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
fetchSessionWithTimeout();
|
|
595
|
-
return () => {
|
|
596
|
-
cancelled = true;
|
|
597
|
-
};
|
|
598
|
-
}, [userNeedsChange, supabase.auth]);
|
|
599
|
-
const {
|
|
600
|
-
data: profileData,
|
|
601
|
-
isLoading: profileLoading,
|
|
602
|
-
refetch: refetchProfile
|
|
603
|
-
} = useDbQuery("core.Profile", {
|
|
604
|
-
where: {
|
|
605
|
-
id: currentUser?.id
|
|
606
|
-
},
|
|
607
|
-
enabled: isUsable(currentUser)
|
|
608
|
-
});
|
|
609
|
-
const profile = profileData?.[0] ?? null;
|
|
610
|
-
const {
|
|
611
|
-
data: directAccess,
|
|
612
|
-
isLoading: directAccessLoading,
|
|
613
|
-
refetch: refetchDirectAccess
|
|
614
|
-
} = useDbQuery("core.UserAccess", {
|
|
615
|
-
where: {
|
|
616
|
-
userId: currentUser?.id
|
|
617
|
-
},
|
|
618
|
-
enabled: isUsable(currentUser)
|
|
619
|
-
});
|
|
620
|
-
const {
|
|
621
|
-
data: userGroups,
|
|
622
|
-
isLoading: userGroupsLoading,
|
|
623
|
-
refetch: refetchUserGroups
|
|
624
|
-
} = useDbQuery("core.UserGroup", {
|
|
625
|
-
where: {
|
|
626
|
-
userId: currentUser?.id
|
|
627
|
-
},
|
|
628
|
-
enabled: isUsable(currentUser)
|
|
629
|
-
});
|
|
630
|
-
const {
|
|
631
|
-
data: groups,
|
|
632
|
-
isLoading: groupsLoading,
|
|
633
|
-
refetch: refetchGroups
|
|
634
|
-
} = useDbQuery("core.Group", {
|
|
635
|
-
where: {
|
|
636
|
-
isActive: 1
|
|
637
|
-
},
|
|
638
|
-
// PowerSync stores booleans as 0/1
|
|
639
|
-
enabled: isUsable(currentUser)
|
|
640
|
-
});
|
|
641
|
-
const groupIds = useMemo3(() => userGroups?.map((ug) => ug.groupId) ?? [], [userGroups]);
|
|
642
|
-
const groupsMap = useMemo3(() => new Map(groups?.map((g) => [g.id, g]) ?? []), [groups]);
|
|
643
|
-
const {
|
|
644
|
-
data: groupAccess,
|
|
645
|
-
isLoading: groupAccessLoading,
|
|
646
|
-
refetch: refetchGroupAccess
|
|
647
|
-
} = useDbQuery("core.GroupAccessKey", {
|
|
648
|
-
where: groupIds.length > 0 ? {
|
|
649
|
-
groupId: {
|
|
650
|
-
in: groupIds
|
|
651
|
-
}
|
|
652
|
-
} : void 0,
|
|
653
|
-
enabled: groupIds.length > 0
|
|
654
|
-
});
|
|
655
|
-
const refetchAccessData = useCallback2(() => {
|
|
656
|
-
refetchDirectAccess();
|
|
657
|
-
refetchUserGroups();
|
|
658
|
-
refetchGroups();
|
|
659
|
-
refetchGroupAccess();
|
|
660
|
-
}, [refetchDirectAccess, refetchUserGroups, refetchGroups, refetchGroupAccess]);
|
|
661
|
-
const userGroupIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
662
|
-
useEffect2(() => {
|
|
663
|
-
if (userGroups) {
|
|
664
|
-
const groupIdSet = /* @__PURE__ */ new Set();
|
|
665
|
-
for (const ug_0 of userGroups) {
|
|
666
|
-
if (ug_0.groupId) {
|
|
667
|
-
groupIdSet.add(typeof ug_0.groupId === "number" ? ug_0.groupId : parseInt(ug_0.groupId, 10));
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
userGroupIdsRef.current = groupIdSet;
|
|
671
|
-
}
|
|
672
|
-
}, [userGroups]);
|
|
673
|
-
useEffect2(() => {
|
|
674
|
-
if (!currentUser?.id) return;
|
|
675
|
-
const channel = supabase.channel(`user-access-keys-${currentUser.id}`).on("postgres_changes", {
|
|
676
|
-
event: "*",
|
|
677
|
-
schema: "core",
|
|
678
|
-
table: "UserAccess",
|
|
679
|
-
filter: `userId=eq.${currentUser.id}`
|
|
680
|
-
}, () => {
|
|
681
|
-
refetchAccessData();
|
|
682
|
-
}).on("postgres_changes", {
|
|
683
|
-
event: "*",
|
|
684
|
-
schema: "core",
|
|
685
|
-
table: "UserGroup",
|
|
686
|
-
filter: `userId=eq.${currentUser.id}`
|
|
687
|
-
}, () => {
|
|
688
|
-
refetchAccessData();
|
|
689
|
-
}).on("postgres_changes", {
|
|
690
|
-
event: "*",
|
|
691
|
-
schema: "core",
|
|
692
|
-
table: "GroupAccessKey"
|
|
693
|
-
}, (payload) => {
|
|
694
|
-
const groupId = payload.new?.groupId || payload.old?.groupId;
|
|
695
|
-
if (groupId && userGroupIdsRef.current.has(groupId)) {
|
|
696
|
-
refetchAccessData();
|
|
697
|
-
}
|
|
698
|
-
}).on("postgres_changes", {
|
|
699
|
-
event: "UPDATE",
|
|
700
|
-
schema: "core",
|
|
701
|
-
table: "Group"
|
|
702
|
-
}, (payload_0) => {
|
|
703
|
-
const oldActive = payload_0.old?.isActive;
|
|
704
|
-
const newActive = payload_0.new?.isActive;
|
|
705
|
-
const groupId_0 = payload_0.new?.id;
|
|
706
|
-
if (oldActive !== newActive && groupId_0 && userGroupIdsRef.current.has(groupId_0)) {
|
|
707
|
-
refetchAccessData();
|
|
708
|
-
}
|
|
709
|
-
}).subscribe();
|
|
710
|
-
return () => {
|
|
711
|
-
channel.unsubscribe();
|
|
712
|
-
supabase.removeChannel(channel);
|
|
713
|
-
};
|
|
714
|
-
}, [supabase, currentUser?.id, refetchAccessData]);
|
|
715
|
-
useEffect2(() => {
|
|
716
|
-
if (!currentUser?.id) return;
|
|
717
|
-
const profileChannel = supabase.channel(`profile-status-${currentUser.id}`).on("postgres_changes", {
|
|
718
|
-
event: "UPDATE",
|
|
719
|
-
schema: "core",
|
|
720
|
-
table: "Profile",
|
|
721
|
-
filter: `id=eq.${currentUser.id}`
|
|
722
|
-
}, (payload_1) => {
|
|
723
|
-
const newStatus = payload_1.new?.status;
|
|
724
|
-
const oldStatus = payload_1.old?.status;
|
|
725
|
-
if (oldStatus === "active" && (newStatus === "archived" || newStatus === "suspended")) {
|
|
726
|
-
signOutAsync();
|
|
727
|
-
}
|
|
728
|
-
refetchProfile();
|
|
729
|
-
}).subscribe();
|
|
730
|
-
return () => {
|
|
731
|
-
profileChannel.unsubscribe();
|
|
732
|
-
supabase.removeChannel(profileChannel);
|
|
733
|
-
};
|
|
734
|
-
}, [supabase, currentUser?.id, refetchProfile]);
|
|
735
|
-
const allAccessKeys = useMemo3(() => {
|
|
736
|
-
const keys = [];
|
|
737
|
-
directAccess?.forEach((a) => {
|
|
738
|
-
if (isNotExpired(a.expiresAt)) {
|
|
739
|
-
keys.push({
|
|
740
|
-
accessKey: a.accessKey,
|
|
741
|
-
effect: a.effect ?? "allow",
|
|
742
|
-
source: "direct",
|
|
743
|
-
expiresAt: a.expiresAt
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
const activeGroupIds = new Set(userGroups?.filter((ug_1) => {
|
|
748
|
-
const group = groupsMap.get(ug_1.groupId);
|
|
749
|
-
return group?.isActive === 1 && isNotExpired(ug_1.expiresAt);
|
|
750
|
-
}).map((ug_2) => ug_2.groupId) ?? []);
|
|
751
|
-
groupAccess?.forEach((ga) => {
|
|
752
|
-
if (activeGroupIds.has(ga.groupId) && isNotExpired(ga.expiresAt)) {
|
|
753
|
-
keys.push({
|
|
754
|
-
accessKey: ga.accessKey,
|
|
755
|
-
effect: ga.effect ?? "allow",
|
|
756
|
-
source: "group",
|
|
757
|
-
expiresAt: ga.expiresAt
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
return keys;
|
|
762
|
-
}, [directAccess, userGroups, groupsMap, groupAccess]);
|
|
763
|
-
const combinedAccess = useMemo3(() => {
|
|
764
|
-
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
765
|
-
for (const item of allAccessKeys) {
|
|
766
|
-
if (item.accessKey && item.effect === "allow") {
|
|
767
|
-
uniqueKeys.add(item.accessKey);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return Array.from(uniqueKeys);
|
|
771
|
-
}, [allAccessKeys]);
|
|
772
|
-
const effectivePermissions = useMemo3(() => {
|
|
773
|
-
const permissions = [];
|
|
774
|
-
for (const item_0 of allAccessKeys) {
|
|
775
|
-
if (item_0.effect !== "allow") continue;
|
|
776
|
-
const parts = item_0.accessKey.split(":");
|
|
777
|
-
if (parts.length === 3) {
|
|
778
|
-
const [resourceType, resourceId, permission] = parts;
|
|
779
|
-
const permissionLevel = getPermissionLevel(permission);
|
|
780
|
-
permissions.push({
|
|
781
|
-
resourceType,
|
|
782
|
-
resourceId,
|
|
783
|
-
permission,
|
|
784
|
-
permissionLevel,
|
|
785
|
-
source: item_0.source,
|
|
786
|
-
inheritedFrom: null,
|
|
787
|
-
expiresAt: item_0.expiresAt ?? null
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
return permissions;
|
|
792
|
-
}, [allAccessKeys]);
|
|
793
|
-
const profileStatus = profile?.status;
|
|
794
|
-
const isArchived = profileStatus === "archived";
|
|
795
|
-
const isSuspended = profileStatus === "suspended";
|
|
796
|
-
const hasAccess = useCallback2((key) => {
|
|
797
|
-
if (isArchived || isSuspended) {
|
|
798
|
-
return false;
|
|
799
|
-
}
|
|
800
|
-
const accessGiven = combinedAccess;
|
|
801
|
-
if (isUsable(accessGiven) === false) return false;
|
|
802
|
-
if (accessGiven.includes("*:*:*")) return true;
|
|
803
|
-
if (accessGiven.includes(key)) return true;
|
|
804
|
-
if (isUsable(key) === false) return true;
|
|
805
|
-
const parts_0 = key.split(":");
|
|
806
|
-
if (parts_0.length === 3) {
|
|
807
|
-
const [type, id_1, action_0] = parts_0;
|
|
808
|
-
const requiredLevel = getPermissionLevel(action_0);
|
|
809
|
-
const hasPermission = effectivePermissions.some((p) => p.resourceType === type && p.resourceId === id_1 && p.permissionLevel >= requiredLevel);
|
|
810
|
-
if (hasPermission) {
|
|
811
|
-
return true;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
return false;
|
|
815
|
-
}, [combinedAccess, effectivePermissions, isArchived, isSuspended]);
|
|
816
|
-
const isAccessLoading = directAccessLoading || userGroupsLoading || groupsLoading || groupAccessLoading;
|
|
817
|
-
const authStateWithLoading = useMemo3(() => ({
|
|
818
|
-
hasAccess,
|
|
819
|
-
user: currentUser,
|
|
820
|
-
profile,
|
|
821
|
-
access: combinedAccess,
|
|
822
|
-
effectivePermissions,
|
|
823
|
-
profileStatus,
|
|
824
|
-
isArchived,
|
|
825
|
-
isSuspended,
|
|
826
|
-
isLoading: currentUser === null ? false : profileLoading || isAccessLoading || currentUser === void 0,
|
|
827
|
-
signInAsync,
|
|
828
|
-
signOutAsync,
|
|
829
|
-
onSignOut,
|
|
830
|
-
removeOnSignOut,
|
|
831
|
-
registerAsync,
|
|
832
|
-
refreshAsync
|
|
833
|
-
}), [profile, profileLoading, isAccessLoading, currentUser, combinedAccess, effectivePermissions, profileStatus, isArchived, isSuspended, hasAccess]);
|
|
834
|
-
const content = enableEntityPermissions ? /* @__PURE__ */ jsx2(PermissionProvider, { children }) : children;
|
|
835
|
-
return /* @__PURE__ */ jsx2(setupAuthContext.Provider, { value: authStateWithLoading, children: content });
|
|
836
|
-
}
|
|
837
|
-
|
|
838
756
|
// src/auth/context/UserMetadataContext.tsx
|
|
839
757
|
import { c as _c2 } from "react/compiler-runtime";
|
|
840
758
|
import { createContext as createContext3, useContext as useContext2, useEffect as useEffect3, useMemo as useMemo4, useState as useState3, useCallback as useCallback3, useRef as useRef4 } from "react";
|
|
@@ -1176,11 +1094,11 @@ export {
|
|
|
1176
1094
|
isTimeoutError,
|
|
1177
1095
|
useDbQuery2 as useDbQuery,
|
|
1178
1096
|
setupAuthContext,
|
|
1097
|
+
AuthProvider,
|
|
1179
1098
|
permissionContext,
|
|
1180
1099
|
entityPermissionContext,
|
|
1181
1100
|
PermissionProvider,
|
|
1182
1101
|
usePermissions,
|
|
1183
|
-
AuthProvider,
|
|
1184
1102
|
userMetadataContext,
|
|
1185
1103
|
UserMetadataProvider,
|
|
1186
1104
|
useUserMetadata,
|
|
@@ -1188,4 +1106,4 @@ export {
|
|
|
1188
1106
|
useSetUserMetadata,
|
|
1189
1107
|
useUserMetadataState
|
|
1190
1108
|
};
|
|
1191
|
-
//# sourceMappingURL=chunk-
|
|
1109
|
+
//# sourceMappingURL=chunk-MZLEDWJF.js.map
|