academe-kit 0.1.6 → 0.1.8
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/index.d.ts +88 -8
- package/dist/index.esm.js +215 -31
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +214 -29
- package/dist/index.js.map +1 -1
- package/dist/types/components/ProtectedApp/index.d.ts +0 -1
- package/dist/types/context/SecurityProvider/types.d.ts +8 -5
- package/dist/types/hooks/useCurrentUser.d.ts +82 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/services/userService.d.ts +79 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var React = require('react');
|
|
5
|
+
var axios = require('axios');
|
|
5
6
|
|
|
6
7
|
function _interopNamespaceDefault(e) {
|
|
7
8
|
var n = Object.create(null);
|
|
@@ -2250,66 +2251,250 @@ function isObject(input) {
|
|
|
2250
2251
|
return typeof input === 'object' && input !== null;
|
|
2251
2252
|
}
|
|
2252
2253
|
|
|
2253
|
-
|
|
2254
|
-
|
|
2254
|
+
/**
|
|
2255
|
+
* User Service - Fetches complete user data from MongoDB
|
|
2256
|
+
* This service interacts with the new unified users collection
|
|
2257
|
+
*/
|
|
2258
|
+
class UserService {
|
|
2259
|
+
constructor(config) {
|
|
2260
|
+
this.cachedUser = null;
|
|
2261
|
+
this.cacheExpiry = 0;
|
|
2262
|
+
this.getAccessToken = config.getAccessToken;
|
|
2263
|
+
this.client = axios.create({
|
|
2264
|
+
baseURL: config.apiBaseUrl,
|
|
2265
|
+
headers: {
|
|
2266
|
+
'Content-Type': 'application/json'
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
// Add auth interceptor
|
|
2270
|
+
this.client.interceptors.request.use((config) => {
|
|
2271
|
+
const token = this.getAccessToken();
|
|
2272
|
+
if (token) {
|
|
2273
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
2274
|
+
}
|
|
2275
|
+
return config;
|
|
2276
|
+
});
|
|
2277
|
+
// Add response interceptor for error handling
|
|
2278
|
+
this.client.interceptors.response.use((response) => response, (error) => {
|
|
2279
|
+
if (error.response?.status === 401) {
|
|
2280
|
+
// Clear cache on authentication error
|
|
2281
|
+
this.clearCache();
|
|
2282
|
+
}
|
|
2283
|
+
return Promise.reject(error);
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Get current user data from MongoDB
|
|
2288
|
+
* Includes caching to reduce API calls
|
|
2289
|
+
*/
|
|
2290
|
+
async getCurrentUser(forceRefresh = false) {
|
|
2291
|
+
try {
|
|
2292
|
+
// Check cache
|
|
2293
|
+
if (!forceRefresh && this.cachedUser && Date.now() < this.cacheExpiry) {
|
|
2294
|
+
return this.cachedUser;
|
|
2295
|
+
}
|
|
2296
|
+
const response = await this.client.get('/api/users/me');
|
|
2297
|
+
if (response.data) {
|
|
2298
|
+
// Cache for 5 minutes
|
|
2299
|
+
this.cachedUser = response.data;
|
|
2300
|
+
this.cacheExpiry = Date.now() + (5 * 60 * 1000);
|
|
2301
|
+
return response.data;
|
|
2302
|
+
}
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
catch (error) {
|
|
2306
|
+
console.error('Error fetching current user:', error);
|
|
2307
|
+
// If it's a 404, user doesn't exist in MongoDB yet
|
|
2308
|
+
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
|
2309
|
+
// User will be auto-created on next API call
|
|
2310
|
+
return null;
|
|
2311
|
+
}
|
|
2312
|
+
throw error;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Update current user data
|
|
2317
|
+
*/
|
|
2318
|
+
async updateCurrentUser(data) {
|
|
2319
|
+
try {
|
|
2320
|
+
// First get current user to get the ID
|
|
2321
|
+
const currentUser = await this.getCurrentUser();
|
|
2322
|
+
if (!currentUser) {
|
|
2323
|
+
throw new Error('User not found');
|
|
2324
|
+
}
|
|
2325
|
+
const response = await this.client.put(`/api/users/${currentUser.id}`, data);
|
|
2326
|
+
if (response.data) {
|
|
2327
|
+
// Update cache
|
|
2328
|
+
this.cachedUser = response.data;
|
|
2329
|
+
this.cacheExpiry = Date.now() + (5 * 60 * 1000);
|
|
2330
|
+
return response.data;
|
|
2331
|
+
}
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
catch (error) {
|
|
2335
|
+
console.error('Error updating user:', error);
|
|
2336
|
+
throw error;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Clear cached user data
|
|
2341
|
+
*/
|
|
2342
|
+
clearCache() {
|
|
2343
|
+
this.cachedUser = null;
|
|
2344
|
+
this.cacheExpiry = 0;
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Check if user has a specific role
|
|
2348
|
+
*/
|
|
2349
|
+
hasRole(user, role) {
|
|
2350
|
+
if (!user || !user.roles)
|
|
2351
|
+
return false;
|
|
2352
|
+
return user.roles.includes(role);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Check if user has access to a specific school
|
|
2356
|
+
*/
|
|
2357
|
+
hasSchoolAccess(user, schoolId) {
|
|
2358
|
+
if (!user)
|
|
2359
|
+
return false;
|
|
2360
|
+
// Admin has access to all schools
|
|
2361
|
+
if (this.hasRole(user, 'admin_academe'))
|
|
2362
|
+
return true;
|
|
2363
|
+
// Check if user's school matches
|
|
2364
|
+
return user.school_id === schoolId;
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Check if user is a student (has student-specific data)
|
|
2368
|
+
*/
|
|
2369
|
+
isStudent(user) {
|
|
2370
|
+
if (!user)
|
|
2371
|
+
return false;
|
|
2372
|
+
return !!user.registration_number || this.hasRole(user, 'student');
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Check if user has completed onboarding
|
|
2376
|
+
*/
|
|
2377
|
+
hasCompletedOnboarding(user) {
|
|
2378
|
+
if (!user)
|
|
2379
|
+
return false;
|
|
2380
|
+
return user.onboarding_completo;
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Check if user has access enabled
|
|
2384
|
+
*/
|
|
2385
|
+
hasAccessEnabled(user) {
|
|
2386
|
+
if (!user)
|
|
2387
|
+
return false;
|
|
2388
|
+
return user.acesso_liberado && user.active;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
// Export singleton instance creator
|
|
2392
|
+
const createUserService = (config) => {
|
|
2393
|
+
return new UserService(config);
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, apiBaseUrl, }) => {
|
|
2397
|
+
return (jsxRuntime.jsx(ReactKeycloakProvider, { authClient: new Keycloak({ clientId, realm, url: keycloakUrl }), children: jsxRuntime.jsx(SecurityProvider, { hubUrl: hubUrl, apiBaseUrl: apiBaseUrl, children: children }) }));
|
|
2255
2398
|
};
|
|
2256
2399
|
const SecurityContext = React.createContext({
|
|
2257
2400
|
isInitialized: false,
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
name: "",
|
|
2261
|
-
lastName: "",
|
|
2262
|
-
attributes: {
|
|
2263
|
-
school_ids: [],
|
|
2264
|
-
},
|
|
2265
|
-
}),
|
|
2401
|
+
user: null,
|
|
2402
|
+
refreshUserData: async () => { },
|
|
2266
2403
|
signOut: () => null,
|
|
2267
2404
|
goToLogin: () => null,
|
|
2268
2405
|
hasSchool: () => false,
|
|
2269
2406
|
hasRealmRole: () => false,
|
|
2270
|
-
redirectToHub: () => null,
|
|
2271
2407
|
hasClientRole: () => false,
|
|
2272
2408
|
isAuthenticated: () => false,
|
|
2273
2409
|
});
|
|
2274
|
-
const SecurityProvider = ({
|
|
2410
|
+
const SecurityProvider = ({ apiBaseUrl = "https://api.academe.com.br", children, }) => {
|
|
2275
2411
|
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
2412
|
+
const [currentUser, setCurrentUser] = React.useState(null);
|
|
2276
2413
|
const { initialized, keycloak } = useKeycloak();
|
|
2414
|
+
const userService = createUserService({
|
|
2415
|
+
apiBaseUrl,
|
|
2416
|
+
getAccessToken: () => keycloak?.token || null,
|
|
2417
|
+
});
|
|
2277
2418
|
React.useEffect(() => {
|
|
2278
2419
|
setIsInitialized(initialized);
|
|
2279
2420
|
}, [initialized]);
|
|
2280
2421
|
React.useEffect(() => {
|
|
2281
2422
|
window.accessToken = keycloak.token;
|
|
2282
2423
|
}, [keycloak.token]);
|
|
2283
|
-
|
|
2424
|
+
// Fetch user data from MongoDB when authenticated
|
|
2425
|
+
React.useEffect(() => {
|
|
2426
|
+
const fetchUserData = async () => {
|
|
2427
|
+
if (initialized && keycloak?.authenticated && keycloak?.token) {
|
|
2428
|
+
try {
|
|
2429
|
+
const mongoUser = await userService.getCurrentUser();
|
|
2430
|
+
if (mongoUser) {
|
|
2431
|
+
const academeUser = {
|
|
2432
|
+
...mongoUser,
|
|
2433
|
+
keycloakUser: getKeycloakUser(),
|
|
2434
|
+
};
|
|
2435
|
+
setCurrentUser(academeUser);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
catch (error) {
|
|
2439
|
+
console.error("Error fetching user data:", error);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
else if (!keycloak?.authenticated) {
|
|
2443
|
+
// Clear user data when not authenticated
|
|
2444
|
+
setCurrentUser(null);
|
|
2445
|
+
userService.clearCache();
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
fetchUserData();
|
|
2449
|
+
}, [initialized, keycloak?.authenticated, keycloak?.token]);
|
|
2450
|
+
const getKeycloakUser = () => {
|
|
2284
2451
|
const idTokenParsed = keycloak?.idTokenParsed || {};
|
|
2285
2452
|
return {
|
|
2286
|
-
email:
|
|
2287
|
-
name:
|
|
2288
|
-
lastName:
|
|
2289
|
-
attributes: {
|
|
2290
|
-
school_ids: (idTokenParsed.school_ids || []),
|
|
2291
|
-
},
|
|
2453
|
+
email: idTokenParsed?.email || "",
|
|
2454
|
+
name: idTokenParsed?.given_name || "",
|
|
2455
|
+
lastName: idTokenParsed?.family_name || "",
|
|
2292
2456
|
};
|
|
2293
2457
|
};
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2458
|
+
const refreshUserData = React.useCallback(async () => {
|
|
2459
|
+
if (keycloak?.authenticated) {
|
|
2460
|
+
try {
|
|
2461
|
+
const mongoUser = await userService.getCurrentUser(true); // Force refresh
|
|
2462
|
+
if (mongoUser) {
|
|
2463
|
+
const academeUser = {
|
|
2464
|
+
...mongoUser,
|
|
2465
|
+
keycloakUser: getKeycloakUser(),
|
|
2466
|
+
};
|
|
2467
|
+
setCurrentUser(academeUser);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
catch (error) {
|
|
2471
|
+
console.error("Error refreshing user data:", error);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}, [keycloak?.authenticated]);
|
|
2297
2475
|
const signOut = () => {
|
|
2476
|
+
userService.clearCache();
|
|
2477
|
+
setCurrentUser(null);
|
|
2298
2478
|
keycloak?.logout();
|
|
2299
2479
|
};
|
|
2300
2480
|
const isAuthenticated = () => {
|
|
2301
2481
|
return (!!keycloak?.idTokenParsed?.email?.length && !!keycloak?.authenticated);
|
|
2302
2482
|
};
|
|
2303
2483
|
const hasSchool = (schoolId) => {
|
|
2304
|
-
|
|
2484
|
+
if (currentUser?.school_id) {
|
|
2485
|
+
return (currentUser.school_id === schoolId ||
|
|
2486
|
+
userService.hasRole(currentUser, "admin_academe"));
|
|
2487
|
+
}
|
|
2488
|
+
// Fallback to Keycloak data
|
|
2489
|
+
return false; // No school_id in Keycloak data
|
|
2305
2490
|
};
|
|
2306
2491
|
return (jsxRuntime.jsx(SecurityContext.Provider, { value: {
|
|
2307
2492
|
isInitialized,
|
|
2308
|
-
|
|
2493
|
+
user: currentUser,
|
|
2494
|
+
refreshUserData,
|
|
2309
2495
|
signOut,
|
|
2310
|
-
redirectToHub,
|
|
2311
2496
|
isAuthenticated,
|
|
2312
|
-
hasSchool
|
|
2497
|
+
hasSchool,
|
|
2313
2498
|
goToLogin: keycloak.login,
|
|
2314
2499
|
hasRealmRole: keycloak?.hasRealmRole,
|
|
2315
2500
|
hasClientRole: keycloak.hasResourceRole,
|
|
@@ -5400,8 +5585,8 @@ function Spinner({ className, ...props }) {
|
|
|
5400
5585
|
return (jsxRuntime.jsx(LoaderCircle, { role: "status", "aria-label": "Loading", className: cn("size-10 animate-spin text-violet-500", className), ...props }));
|
|
5401
5586
|
}
|
|
5402
5587
|
|
|
5403
|
-
const ProtectedApp = ({ children,
|
|
5404
|
-
const { isInitialized, goToLogin, hasClientRole,
|
|
5588
|
+
const ProtectedApp = ({ children, requiredClientRoles, }) => {
|
|
5589
|
+
const { isInitialized, goToLogin, hasClientRole, isAuthenticated } = useAcademeAuth();
|
|
5405
5590
|
if (!isInitialized) {
|
|
5406
5591
|
return (jsxRuntime.jsx("div", { className: "flex w-screen h-screen items-center justify-center", children: jsxRuntime.jsx(Spinner, { className: "size-10 text-violet-500" }) }));
|
|
5407
5592
|
}
|
|
@@ -5411,7 +5596,6 @@ const ProtectedApp = ({ children, unauthorizedMessage, requiredClientRoles, }) =
|
|
|
5411
5596
|
}
|
|
5412
5597
|
if (requiredClientRoles &&
|
|
5413
5598
|
!requiredClientRoles?.some((role) => hasClientRole(role))) {
|
|
5414
|
-
redirectToHub(unauthorizedMessage || "Unauthorized");
|
|
5415
5599
|
return jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
5416
5600
|
}
|
|
5417
5601
|
return children;
|
|
@@ -5479,5 +5663,6 @@ exports.ProtectedComponent = ProtectedComponent;
|
|
|
5479
5663
|
exports.ProtectedRouter = ProtectedRouter;
|
|
5480
5664
|
exports.Spinner = Spinner;
|
|
5481
5665
|
exports.cn = cn;
|
|
5666
|
+
exports.createUserService = createUserService;
|
|
5482
5667
|
exports.useAcademeAuth = useAcademeAuth;
|
|
5483
5668
|
//# sourceMappingURL=index.js.map
|