nucleus-core-ts 0.9.76 → 0.9.77
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/fe/components/AuthGuard/LoginChecker.d.ts +10 -0
- package/dist/fe/components/AuthGuard/LoginChecker.js +72 -0
- package/dist/fe/components/AuthGuard/RoleChecker.d.ts +9 -0
- package/dist/fe/components/AuthGuard/RoleChecker.js +80 -0
- package/dist/fe/components/AuthGuard/index.d.ts +5 -0
- package/dist/fe/components/AuthGuard/index.js +4 -0
- package/dist/fe/components/AuthGuard/permissions.d.ts +14 -0
- package/dist/fe/components/AuthGuard/permissions.js +47 -0
- package/dist/fe/components/AuthGuard/store.d.ts +2 -0
- package/dist/fe/components/AuthGuard/store.js +147 -0
- package/dist/fe/components/AuthGuard/types.d.ts +91 -0
- package/dist/fe/components/AuthGuard/types.js +1 -0
- package/dist/fe/index.d.ts +2 -0
- package/dist/fe/index.js +2 -0
- package/dist/index.js +9 -9
- package/dist/src/ElysiaPlugin/routes/auth/admin/impersonate.d.ts +1 -0
- package/dist/src/ElysiaPlugin/routes/auth/me/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AuthGuardConfig } from './types';
|
|
3
|
+
type LoginCheckerProps = {
|
|
4
|
+
config: AuthGuardConfig;
|
|
5
|
+
pathname: string;
|
|
6
|
+
onNavigate: (path: string) => void;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare function LoginChecker({ config, pathname, onNavigate, children }: LoginCheckerProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useEffectEvent, useRef } from 'react';
|
|
4
|
+
import { useAuthGuardStore } from './store';
|
|
5
|
+
// ─── Path Matching ──────────────────────────────────────────────────────────
|
|
6
|
+
function matchesAnyPath(pathname, paths) {
|
|
7
|
+
return paths.some((p)=>pathname === p || pathname.startsWith(`${p}/`));
|
|
8
|
+
}
|
|
9
|
+
// ─── LoginChecker ───────────────────────────────────────────────────────────
|
|
10
|
+
// Wraps the application tree. Determines whether the current path requires
|
|
11
|
+
// authentication and, if so, fetches the user via the ME endpoint.
|
|
12
|
+
// - unauthPaths: login/register pages — skip checks, render children
|
|
13
|
+
// - publicPaths: public content — skip checks, render children
|
|
14
|
+
// - All other paths: require authenticated user in store
|
|
15
|
+
//
|
|
16
|
+
// The component calls `config.fetchUser` exactly once per session bootstrap.
|
|
17
|
+
// Subsequent navigations reuse the hydrated store. A full re-fetch only
|
|
18
|
+
// happens after logout (store.reset) or hard refresh.
|
|
19
|
+
const MAX_RETRIES = 2;
|
|
20
|
+
const RETRY_DELAY_MS = 1000;
|
|
21
|
+
export function LoginChecker({ config, pathname, onNavigate, children }) {
|
|
22
|
+
const store = useAuthGuardStore();
|
|
23
|
+
const isCheckingRef = useRef(false);
|
|
24
|
+
const retryCountRef = useRef(0);
|
|
25
|
+
const isUnauth = matchesAnyPath(pathname, config.unauthPaths);
|
|
26
|
+
const isPublic = matchesAnyPath(pathname, config.publicPaths);
|
|
27
|
+
const requiresAuth = !isUnauth && !isPublic;
|
|
28
|
+
const performAuthCheck = useEffectEvent(()=>{
|
|
29
|
+
if (isCheckingRef.current) return;
|
|
30
|
+
isCheckingRef.current = true;
|
|
31
|
+
store.setLoading(true);
|
|
32
|
+
config.fetchUser({
|
|
33
|
+
onSuccess: (data)=>{
|
|
34
|
+
store.hydrate(data);
|
|
35
|
+
isCheckingRef.current = false;
|
|
36
|
+
retryCountRef.current = 0;
|
|
37
|
+
},
|
|
38
|
+
onError: (_error, _code)=>{
|
|
39
|
+
isCheckingRef.current = false;
|
|
40
|
+
if (retryCountRef.current < MAX_RETRIES) {
|
|
41
|
+
retryCountRef.current++;
|
|
42
|
+
setTimeout(()=>performAuthCheck(), RETRY_DELAY_MS);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
store.setLoginChecked(true);
|
|
46
|
+
store.setLoading(false);
|
|
47
|
+
retryCountRef.current = 0;
|
|
48
|
+
onNavigate(config.loginPath);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
useEffect(()=>{
|
|
53
|
+
if (!requiresAuth) return;
|
|
54
|
+
if (store.user) return;
|
|
55
|
+
if (isCheckingRef.current) return;
|
|
56
|
+
performAuthCheck();
|
|
57
|
+
}, [
|
|
58
|
+
requiresAuth,
|
|
59
|
+
store.user,
|
|
60
|
+
pathname
|
|
61
|
+
]);
|
|
62
|
+
if (!requiresAuth) return /*#__PURE__*/ _jsx(_Fragment, {
|
|
63
|
+
children: children
|
|
64
|
+
});
|
|
65
|
+
const showLoader = !store.isLoginChecked || !store.user;
|
|
66
|
+
if (showLoader) return /*#__PURE__*/ _jsx(_Fragment, {
|
|
67
|
+
children: config.loadingComponent ?? null
|
|
68
|
+
});
|
|
69
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
70
|
+
children: children
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AuthGuardConfig } from './types';
|
|
3
|
+
type RoleCheckerProps = {
|
|
4
|
+
config: AuthGuardConfig;
|
|
5
|
+
pathname: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export declare function RoleChecker({ config, pathname, children }: RoleCheckerProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
|
+
import { useAuthGuardStore } from './store';
|
|
5
|
+
// ─── Path Matching ──────────────────────────────────────────────────────────
|
|
6
|
+
function matchesAnyPath(pathname, paths) {
|
|
7
|
+
return paths.some((p)=>pathname === p || pathname.startsWith(`${p}/`));
|
|
8
|
+
}
|
|
9
|
+
function getRouteRequirement(pathname, requirements) {
|
|
10
|
+
if (requirements[pathname]) return requirements[pathname];
|
|
11
|
+
for (const [configPath, req] of Object.entries(requirements)){
|
|
12
|
+
if (pathname.startsWith(`${configPath}/`)) return req;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function RoleChecker({ config, pathname, children }) {
|
|
17
|
+
const store = useAuthGuardStore();
|
|
18
|
+
const [state, setState] = useState('idle');
|
|
19
|
+
const evaluateAccess = useEffectEvent(()=>{
|
|
20
|
+
if (matchesAnyPath(pathname, config.unauthPaths) || matchesAnyPath(pathname, config.publicPaths)) {
|
|
21
|
+
setState('authorized');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!store.isLoginChecked || !store.user) {
|
|
25
|
+
setState('checking');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (store.isGodmin()) {
|
|
29
|
+
setState('authorized');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (config.globalRoleGate && config.globalRoleGate.length > 0) {
|
|
33
|
+
const passesGate = store.hasAnyRole(config.globalRoleGate);
|
|
34
|
+
if (!passesGate) {
|
|
35
|
+
setState('forbidden');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const requirement = config.routeRequirements ? getRouteRequirement(pathname, config.routeRequirements) : null;
|
|
40
|
+
if (!requirement) {
|
|
41
|
+
setState('authorized');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const hasRequiredRoles = !requirement.roles || requirement.roles.length === 0 || (requirement.requireAll ? store.hasAllRoles(requirement.roles) : store.hasAnyRole(requirement.roles));
|
|
45
|
+
const hasRequiredClaims = !requirement.claims || requirement.claims.length === 0 || (requirement.requireAll ? store.hasAllClaims(requirement.claims, requirement.scope) : store.hasAnyClaim(requirement.claims, requirement.scope));
|
|
46
|
+
if (requirement.roles?.length && requirement.claims?.length) {
|
|
47
|
+
if (hasRequiredRoles || hasRequiredClaims) {
|
|
48
|
+
setState('authorized');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setState('forbidden');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (requirement.roles?.length && !hasRequiredRoles) {
|
|
55
|
+
setState('forbidden');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (requirement.claims?.length && !hasRequiredClaims) {
|
|
59
|
+
setState('forbidden');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setState('authorized');
|
|
63
|
+
});
|
|
64
|
+
useEffect(()=>{
|
|
65
|
+
evaluateAccess();
|
|
66
|
+
}, [
|
|
67
|
+
pathname,
|
|
68
|
+
store.user,
|
|
69
|
+
store.isLoginChecked,
|
|
70
|
+
store.roles,
|
|
71
|
+
store.claims
|
|
72
|
+
]);
|
|
73
|
+
if (state === 'checking' || state === 'idle') return null;
|
|
74
|
+
if (state === 'forbidden') return /*#__PURE__*/ _jsx(_Fragment, {
|
|
75
|
+
children: config.forbiddenComponent ?? null
|
|
76
|
+
});
|
|
77
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
78
|
+
children: children
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { LoginChecker } from './LoginChecker';
|
|
2
|
+
export { RoleChecker } from './RoleChecker';
|
|
3
|
+
export { useAuthGuardStore } from './store';
|
|
4
|
+
export { usePermission } from './permissions';
|
|
5
|
+
export type { AuthActions, AuthClaim, AuthFile, AuthGuardConfig, AuthProfile, AuthRole, AuthState, AuthUser, MeResponseData, PermissionCheckMode, PermissionCheckOptions, RouteRequirement, ScopeFilter, } from './types';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PermissionCheckOptions, ScopeFilter } from './types';
|
|
2
|
+
export declare function usePermission(): {
|
|
3
|
+
checkPermission: (roles: string[], claims: string[], options?: PermissionCheckOptions) => boolean;
|
|
4
|
+
hasRole: (roleName: string) => boolean;
|
|
5
|
+
hasAnyRole: (roleNames: string[]) => boolean;
|
|
6
|
+
hasClaim: (claimAction: string, scope?: ScopeFilter) => boolean;
|
|
7
|
+
hasAnyClaim: (claimActions: string[], scope?: ScopeFilter) => boolean;
|
|
8
|
+
hasAllClaims: (claimActions: string[], scope?: ScopeFilter) => boolean;
|
|
9
|
+
isGodmin: () => boolean;
|
|
10
|
+
user: import("./types").AuthUser | null;
|
|
11
|
+
roles: import("./types").AuthRole[];
|
|
12
|
+
claims: import("./types").AuthClaim[];
|
|
13
|
+
isAuthenticated: boolean;
|
|
14
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useAuthGuardStore } from './store';
|
|
3
|
+
// ─── Hook-based Permission Check ────────────────────────────────────────────
|
|
4
|
+
// Returns a reactive `checkUserPermission` function bound to the current
|
|
5
|
+
// auth state. Use in components for granular visibility/disabled control.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// const { checkPermission, hasClaim, hasRole } = usePermission()
|
|
9
|
+
// <Button disabled={!checkPermission(["supervisor"], ["create.orders"])} />
|
|
10
|
+
// <section hidden={!hasClaim("get.analytics", { orgId: currentOrg })} />
|
|
11
|
+
export function usePermission() {
|
|
12
|
+
const store = useAuthGuardStore();
|
|
13
|
+
const checkPermission = (roles, claims, options)=>{
|
|
14
|
+
return store.checkPermission(roles, claims, options);
|
|
15
|
+
};
|
|
16
|
+
const hasRole = (roleName)=>{
|
|
17
|
+
return store.hasRole(roleName);
|
|
18
|
+
};
|
|
19
|
+
const hasAnyRole = (roleNames)=>{
|
|
20
|
+
return store.hasAnyRole(roleNames);
|
|
21
|
+
};
|
|
22
|
+
const hasClaim = (claimAction, scope)=>{
|
|
23
|
+
return store.hasClaim(claimAction, scope);
|
|
24
|
+
};
|
|
25
|
+
const hasAnyClaim = (claimActions, scope)=>{
|
|
26
|
+
return store.hasAnyClaim(claimActions, scope);
|
|
27
|
+
};
|
|
28
|
+
const hasAllClaims = (claimActions, scope)=>{
|
|
29
|
+
return store.hasAllClaims(claimActions, scope);
|
|
30
|
+
};
|
|
31
|
+
const isGodmin = ()=>{
|
|
32
|
+
return store.isGodmin();
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
checkPermission,
|
|
36
|
+
hasRole,
|
|
37
|
+
hasAnyRole,
|
|
38
|
+
hasClaim,
|
|
39
|
+
hasAnyClaim,
|
|
40
|
+
hasAllClaims,
|
|
41
|
+
isGodmin,
|
|
42
|
+
user: store.user,
|
|
43
|
+
roles: store.roles,
|
|
44
|
+
claims: store.claims,
|
|
45
|
+
isAuthenticated: store.isLoginChecked && store.user !== null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { batch, createStore } from 'h-state';
|
|
3
|
+
// ─── Integrity ──────────────────────────────────────────────────────────────
|
|
4
|
+
// Runtime-generated key lives in closure scope — not accessible via DevTools
|
|
5
|
+
// state inspection. This is defense-in-depth; server middleware is the real guard.
|
|
6
|
+
const _runtimeKey = crypto.getRandomValues(new Uint8Array(32)).join('');
|
|
7
|
+
function computeIntegrityHash(roles, claims) {
|
|
8
|
+
const payload = JSON.stringify({
|
|
9
|
+
r: roles.map((r)=>r.name).sort(),
|
|
10
|
+
c: claims.map((c)=>`${c.action}|${c.scope ?? ''}`).sort()
|
|
11
|
+
});
|
|
12
|
+
let hash = 0;
|
|
13
|
+
const combined = _runtimeKey + payload;
|
|
14
|
+
for(let i = 0; i < combined.length; i++){
|
|
15
|
+
hash = (hash << 5) - hash + combined.charCodeAt(i) | 0;
|
|
16
|
+
}
|
|
17
|
+
return hash.toString(36);
|
|
18
|
+
}
|
|
19
|
+
// ─── Claim Matching ─────────────────────────────────────────────────────────
|
|
20
|
+
// Mirrors nucleus-core backend's claimMatches logic:
|
|
21
|
+
// "get.users" matches "get.users.firstName" (prefix/hierarchical match)
|
|
22
|
+
function claimMatches(userClaim, requiredPattern) {
|
|
23
|
+
if (userClaim === requiredPattern) return true;
|
|
24
|
+
const userParts = userClaim.split('.');
|
|
25
|
+
const requiredParts = requiredPattern.split('.');
|
|
26
|
+
if (userParts.length > requiredParts.length) return false;
|
|
27
|
+
for(let i = 0; i < userParts.length; i++){
|
|
28
|
+
if (userParts[i] !== requiredParts[i]) return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function scopeMatches(claimScope, requiredScope) {
|
|
33
|
+
if (!requiredScope || Object.keys(requiredScope).length === 0) return true;
|
|
34
|
+
if (!claimScope) return true;
|
|
35
|
+
const parsed = new URLSearchParams(claimScope);
|
|
36
|
+
for (const [key, value] of Object.entries(requiredScope)){
|
|
37
|
+
const scopeVal = parsed.get(key);
|
|
38
|
+
if (scopeVal && scopeVal !== value && !scopeVal.startsWith('self:')) return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
function userHasClaim(claims, claimAction, scope) {
|
|
43
|
+
return claims.some((c)=>claimMatches(c.action, claimAction) && scopeMatches(c.scope, scope ?? {}));
|
|
44
|
+
}
|
|
45
|
+
// ─── Store ──────────────────────────────────────────────────────────────────
|
|
46
|
+
const INITIAL_STATE = {
|
|
47
|
+
user: null,
|
|
48
|
+
profile: null,
|
|
49
|
+
files: [],
|
|
50
|
+
roles: [],
|
|
51
|
+
claims: [],
|
|
52
|
+
isLoginChecked: false,
|
|
53
|
+
isLoading: false,
|
|
54
|
+
_integrityHash: ''
|
|
55
|
+
};
|
|
56
|
+
export const { useStore: useAuthGuardStore } = createStore({
|
|
57
|
+
...INITIAL_STATE
|
|
58
|
+
}, {
|
|
59
|
+
hydrate: (store)=>(data)=>{
|
|
60
|
+
const roles = data.roles ?? [];
|
|
61
|
+
const claims = data.claims ?? [];
|
|
62
|
+
batch(()=>{
|
|
63
|
+
store.user = data.user;
|
|
64
|
+
store.profile = data.profile ?? null;
|
|
65
|
+
store.files = data.files ?? [];
|
|
66
|
+
store.roles = roles;
|
|
67
|
+
store.claims = claims;
|
|
68
|
+
store.isLoginChecked = true;
|
|
69
|
+
store.isLoading = false;
|
|
70
|
+
store._integrityHash = computeIntegrityHash(roles, claims);
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
setLoading: (store)=>(loading)=>{
|
|
74
|
+
store.isLoading = loading;
|
|
75
|
+
},
|
|
76
|
+
setLoginChecked: (store)=>(checked)=>{
|
|
77
|
+
store.isLoginChecked = checked;
|
|
78
|
+
},
|
|
79
|
+
reset: (store)=>()=>{
|
|
80
|
+
batch(()=>{
|
|
81
|
+
store.user = INITIAL_STATE.user;
|
|
82
|
+
store.profile = INITIAL_STATE.profile;
|
|
83
|
+
store.files = [
|
|
84
|
+
...INITIAL_STATE.files
|
|
85
|
+
];
|
|
86
|
+
store.roles = [
|
|
87
|
+
...INITIAL_STATE.roles
|
|
88
|
+
];
|
|
89
|
+
store.claims = [
|
|
90
|
+
...INITIAL_STATE.claims
|
|
91
|
+
];
|
|
92
|
+
store.isLoginChecked = false;
|
|
93
|
+
store.isLoading = false;
|
|
94
|
+
store._integrityHash = '';
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
getEffectiveUserId: (store)=>()=>{
|
|
98
|
+
return store.user?.id;
|
|
99
|
+
},
|
|
100
|
+
isGodmin: (store)=>()=>{
|
|
101
|
+
if (store.user?.isGod) return true;
|
|
102
|
+
return store.roles.some((r)=>r.name === 'godmin');
|
|
103
|
+
},
|
|
104
|
+
hasRole: (store)=>(roleName)=>{
|
|
105
|
+
if (store.user?.isGod) return true;
|
|
106
|
+
return store.roles.some((r)=>r.name === roleName);
|
|
107
|
+
},
|
|
108
|
+
hasAnyRole: (store)=>(roleNames)=>{
|
|
109
|
+
if (store.user?.isGod) return true;
|
|
110
|
+
return roleNames.some((name)=>store.roles.some((r)=>r.name === name));
|
|
111
|
+
},
|
|
112
|
+
hasAllRoles: (store)=>(roleNames)=>{
|
|
113
|
+
if (store.user?.isGod) return true;
|
|
114
|
+
return roleNames.every((name)=>store.roles.some((r)=>r.name === name));
|
|
115
|
+
},
|
|
116
|
+
hasClaim: (store)=>(claimAction, scope)=>{
|
|
117
|
+
if (store.user?.isGod) return true;
|
|
118
|
+
if (!store.verifyIntegrity()) return false;
|
|
119
|
+
return userHasClaim(store.claims, claimAction, scope);
|
|
120
|
+
},
|
|
121
|
+
hasAnyClaim: (store)=>(claimActions, scope)=>{
|
|
122
|
+
if (store.user?.isGod) return true;
|
|
123
|
+
if (!store.verifyIntegrity()) return false;
|
|
124
|
+
return claimActions.some((action)=>userHasClaim(store.claims, action, scope));
|
|
125
|
+
},
|
|
126
|
+
hasAllClaims: (store)=>(claimActions, scope)=>{
|
|
127
|
+
if (store.user?.isGod) return true;
|
|
128
|
+
if (!store.verifyIntegrity()) return false;
|
|
129
|
+
return claimActions.every((action)=>userHasClaim(store.claims, action, scope));
|
|
130
|
+
},
|
|
131
|
+
checkPermission: (store)=>(roles, claims, options)=>{
|
|
132
|
+
if (store.user?.isGod) return true;
|
|
133
|
+
if (!store.verifyIntegrity()) return false;
|
|
134
|
+
const mode = options?.mode ?? 'any';
|
|
135
|
+
const scope = options?.scope;
|
|
136
|
+
const rolePass = roles.length === 0 ? false : roles.some((name)=>store.roles.some((r)=>r.name === name));
|
|
137
|
+
const claimPass = claims.length === 0 ? false : mode === 'any' ? claims.some((action)=>userHasClaim(store.claims, action, scope)) : claims.every((action)=>userHasClaim(store.claims, action, scope));
|
|
138
|
+
if (roles.length === 0) return claimPass;
|
|
139
|
+
if (claims.length === 0) return rolePass;
|
|
140
|
+
return rolePass || claimPass;
|
|
141
|
+
},
|
|
142
|
+
verifyIntegrity: (store)=>()=>{
|
|
143
|
+
if (!store._integrityHash) return true;
|
|
144
|
+
const expected = computeIntegrityHash(store.roles, store.claims);
|
|
145
|
+
return expected === store._integrityHash;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type AuthUser = {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string | null;
|
|
5
|
+
isGod?: boolean;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type AuthProfile = {
|
|
9
|
+
id: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
firstName?: string;
|
|
12
|
+
lastName?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
export type AuthFile = {
|
|
16
|
+
id: string;
|
|
17
|
+
type: string | null;
|
|
18
|
+
updatedAt: string | Date;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
export type AuthRole = {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
export type AuthClaim = {
|
|
27
|
+
action: string;
|
|
28
|
+
scope: string | null;
|
|
29
|
+
};
|
|
30
|
+
export type ScopeFilter = Record<string, string>;
|
|
31
|
+
export type PermissionCheckMode = 'any' | 'all';
|
|
32
|
+
export type PermissionCheckOptions = {
|
|
33
|
+
mode?: PermissionCheckMode;
|
|
34
|
+
scope?: ScopeFilter;
|
|
35
|
+
};
|
|
36
|
+
export type RouteRequirement = {
|
|
37
|
+
roles?: string[];
|
|
38
|
+
claims?: string[];
|
|
39
|
+
requireAll?: boolean;
|
|
40
|
+
scope?: ScopeFilter;
|
|
41
|
+
};
|
|
42
|
+
export type AuthGuardConfig = {
|
|
43
|
+
unauthPaths: string[];
|
|
44
|
+
publicPaths: string[];
|
|
45
|
+
loginPath: string;
|
|
46
|
+
forbiddenPath?: string;
|
|
47
|
+
routeRequirements?: Record<string, RouteRequirement>;
|
|
48
|
+
globalRoleGate?: string[];
|
|
49
|
+
fetchUser: (options: {
|
|
50
|
+
onSuccess: (data: MeResponseData) => void;
|
|
51
|
+
onError: (error: unknown, code?: number) => void;
|
|
52
|
+
}) => void;
|
|
53
|
+
onLogout?: () => void;
|
|
54
|
+
loadingComponent?: ReactNode;
|
|
55
|
+
forbiddenComponent?: ReactNode;
|
|
56
|
+
};
|
|
57
|
+
export type MeResponseData = {
|
|
58
|
+
user: AuthUser;
|
|
59
|
+
profile?: AuthProfile | null;
|
|
60
|
+
files?: AuthFile[];
|
|
61
|
+
roles?: AuthRole[];
|
|
62
|
+
claims?: AuthClaim[];
|
|
63
|
+
addresses?: Record<string, unknown>[];
|
|
64
|
+
phones?: Record<string, unknown>[];
|
|
65
|
+
};
|
|
66
|
+
export type AuthState = {
|
|
67
|
+
user: AuthUser | null;
|
|
68
|
+
profile: AuthProfile | null;
|
|
69
|
+
files: AuthFile[];
|
|
70
|
+
roles: AuthRole[];
|
|
71
|
+
claims: AuthClaim[];
|
|
72
|
+
isLoginChecked: boolean;
|
|
73
|
+
isLoading: boolean;
|
|
74
|
+
_integrityHash: string;
|
|
75
|
+
};
|
|
76
|
+
export type AuthActions = {
|
|
77
|
+
hydrate: (data: MeResponseData) => void;
|
|
78
|
+
setLoading: (loading: boolean) => void;
|
|
79
|
+
setLoginChecked: (checked: boolean) => void;
|
|
80
|
+
reset: () => void;
|
|
81
|
+
getEffectiveUserId: () => string | undefined;
|
|
82
|
+
isGodmin: () => boolean;
|
|
83
|
+
hasRole: (roleName: string) => boolean;
|
|
84
|
+
hasAnyRole: (roleNames: string[]) => boolean;
|
|
85
|
+
hasAllRoles: (roleNames: string[]) => boolean;
|
|
86
|
+
hasClaim: (claimAction: string, scope?: ScopeFilter) => boolean;
|
|
87
|
+
hasAnyClaim: (claimActions: string[], scope?: ScopeFilter) => boolean;
|
|
88
|
+
hasAllClaims: (claimActions: string[], scope?: ScopeFilter) => boolean;
|
|
89
|
+
checkPermission: (roles: string[], claims: string[], options?: PermissionCheckOptions) => boolean;
|
|
90
|
+
verifyIntegrity: () => boolean;
|
|
91
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/fe/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { LoginChecker, RoleChecker, useAuthGuardStore, usePermission } from './components/AuthGuard';
|
|
2
|
+
export type { AuthActions, AuthClaim, AuthFile, AuthGuardConfig, AuthProfile, AuthRole, AuthState, AuthUser, MeResponseData as AuthGuardMeResponse, PermissionCheckMode, PermissionCheckOptions, RouteRequirement, ScopeFilter, } from './components/AuthGuard';
|
|
1
3
|
export { AbstractAnimatedBackground } from './components/AbstractAnimatedBackground';
|
|
2
4
|
export type { AuthorizationPageProps, AuthorizationPageTheme, ClaimListProps, RoleClaimEditorProps, RoleListProps, } from './components/AuthorizationPage';
|
|
3
5
|
export { AuthorizationPage, authorizationPageTheme, ClaimList, extendAuthorizationPageTheme, RoleClaimEditor, RoleList, useAuthorizationStore, } from './components/AuthorizationPage';
|
package/dist/fe/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Auth Guard
|
|
2
|
+
export { LoginChecker, RoleChecker, useAuthGuardStore, usePermission } from './components/AuthGuard';
|
|
1
3
|
// UI Components
|
|
2
4
|
export { AbstractAnimatedBackground } from './components/AbstractAnimatedBackground';
|
|
3
5
|
export { AuthorizationPage, authorizationPageTheme, ClaimList, extendAuthorizationPageTheme, RoleClaimEditor, RoleList, useAuthorizationStore } from './components/AuthorizationPage';
|