nextauthz 1.3.34 → 1.3.36
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.mts +5 -8
- package/dist/index.d.ts +5 -8
- package/dist/index.js +60 -68
- package/dist/index.mjs +67 -69
- package/package.json +1 -1
- package/readme.md +1 -1
- package/src/AuthGuard.tsx +11 -18
- package/src/AuthProvider.tsx +72 -92
- package/src/RoleGuard.tsx +15 -21
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
type User = {
|
|
5
5
|
[key: string]: any;
|
|
@@ -24,22 +24,19 @@ declare function createAuthContext<UserType extends User = User>(option?: {
|
|
|
24
24
|
children: ReactNode;
|
|
25
25
|
}) => react_jsx_runtime.JSX.Element;
|
|
26
26
|
useAuth: () => AuthContextType<UserType>;
|
|
27
|
-
tokenKey: string;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
declare const AuthGuard: ({ children, redirectTo, fallback, }: {
|
|
31
30
|
children: React.ReactNode;
|
|
32
31
|
redirectTo?: string;
|
|
33
32
|
fallback?: React.ReactNode;
|
|
34
|
-
};
|
|
35
|
-
declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
33
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: {
|
|
38
36
|
children: React.ReactNode;
|
|
39
37
|
allowedRoles: string[];
|
|
40
38
|
redirectTo?: string;
|
|
41
39
|
fallback?: React.ReactNode;
|
|
42
|
-
};
|
|
43
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
40
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
44
41
|
|
|
45
42
|
export { AuthGuard, RoleGuard, createAuthContext };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
type User = {
|
|
5
5
|
[key: string]: any;
|
|
@@ -24,22 +24,19 @@ declare function createAuthContext<UserType extends User = User>(option?: {
|
|
|
24
24
|
children: ReactNode;
|
|
25
25
|
}) => react_jsx_runtime.JSX.Element;
|
|
26
26
|
useAuth: () => AuthContextType<UserType>;
|
|
27
|
-
tokenKey: string;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
declare const AuthGuard: ({ children, redirectTo, fallback, }: {
|
|
31
30
|
children: React.ReactNode;
|
|
32
31
|
redirectTo?: string;
|
|
33
32
|
fallback?: React.ReactNode;
|
|
34
|
-
};
|
|
35
|
-
declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
33
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: {
|
|
38
36
|
children: React.ReactNode;
|
|
39
37
|
allowedRoles: string[];
|
|
40
38
|
redirectTo?: string;
|
|
41
39
|
fallback?: React.ReactNode;
|
|
42
|
-
};
|
|
43
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
40
|
+
}) => react_jsx_runtime.JSX.Element | null;
|
|
44
41
|
|
|
45
42
|
export { AuthGuard, RoleGuard, createAuthContext };
|
package/dist/index.js
CHANGED
|
@@ -65,87 +65,82 @@ function createAuthContext(option) {
|
|
|
65
65
|
const storage = option?.storage ?? "cookie";
|
|
66
66
|
const tokenKey = option?.tokenKey ?? "access_token";
|
|
67
67
|
const rolePath = option?.rolePath ?? "role";
|
|
68
|
+
const manager = (0, import_react_token_manager.useTokenManager)();
|
|
69
|
+
const user = useAuthStore((s) => s.user);
|
|
70
|
+
const role = useAuthStore((s) => s.role);
|
|
71
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
72
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
73
|
+
const setUser = useAuthStore((s) => s.setUser);
|
|
74
|
+
const setRole = useAuthStore((s) => s.setRole);
|
|
75
|
+
const setAuth = useAuthStore((s) => s.setAuth);
|
|
76
|
+
const setAuthChecked = useAuthStore((s) => s.setAuthChecked);
|
|
77
|
+
const resetAuth = useAuthStore((s) => s.resetAuth);
|
|
68
78
|
(0, import_react.useEffect)(() => {
|
|
69
79
|
(0, import_react_token_manager.configureTokenManager)({ storage });
|
|
70
80
|
}, [storage]);
|
|
71
|
-
const manager = (0, import_react_token_manager.useTokenManager)();
|
|
72
|
-
const {
|
|
73
|
-
user,
|
|
74
|
-
role,
|
|
75
|
-
setUser,
|
|
76
|
-
setRole,
|
|
77
|
-
resetAuth,
|
|
78
|
-
isAuthChecked,
|
|
79
|
-
isAuthenticated,
|
|
80
|
-
setAuth,
|
|
81
|
-
setAuthChecked
|
|
82
|
-
} = useAuthStore();
|
|
83
81
|
const extractRole = (userObj) => {
|
|
84
|
-
if (!userObj
|
|
82
|
+
if (!userObj) return null;
|
|
85
83
|
return rolePath.split(".").reduce((acc, key) => acc?.[key], userObj) ?? null;
|
|
86
84
|
};
|
|
87
85
|
(0, import_react.useEffect)(() => {
|
|
88
86
|
const storedUser = manager.getSingleToken("user");
|
|
89
87
|
const token = manager.getSingleToken(tokenKey);
|
|
90
88
|
if (token && !manager.isExpired(token)) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
setUser(
|
|
96
|
-
setRole(extractRole(
|
|
89
|
+
setAuth(true);
|
|
90
|
+
if (storedUser) {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(storedUser);
|
|
93
|
+
setUser(parsed);
|
|
94
|
+
setRole(extractRole(parsed));
|
|
95
|
+
} catch {
|
|
96
|
+
resetAuth();
|
|
97
97
|
}
|
|
98
|
-
} catch {
|
|
99
|
-
resetAuth();
|
|
100
98
|
}
|
|
101
99
|
} else {
|
|
102
100
|
resetAuth();
|
|
103
101
|
}
|
|
104
102
|
setAuthChecked(true);
|
|
105
103
|
}, [tokenKey]);
|
|
106
|
-
const login = (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
const login = (0, import_react.useCallback)(
|
|
105
|
+
(tokens, userData, role2) => {
|
|
106
|
+
const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
|
|
107
|
+
manager.setTokens({
|
|
108
|
+
...tokens,
|
|
109
|
+
[tokenKey]: tokenValue,
|
|
110
|
+
user: JSON.stringify(userData ?? null)
|
|
111
|
+
});
|
|
112
|
+
if (userData) setUser(userData);
|
|
113
|
+
setRole(role2 ?? extractRole(userData));
|
|
114
|
+
setAuth(true);
|
|
115
|
+
setAuthChecked(true);
|
|
116
|
+
},
|
|
117
|
+
[tokenKey]
|
|
118
|
+
);
|
|
119
|
+
const logout = (0, import_react.useCallback)(() => {
|
|
119
120
|
manager.clearTokens();
|
|
120
121
|
resetAuth();
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
children
|
|
136
|
-
}
|
|
122
|
+
}, []);
|
|
123
|
+
const value = (0, import_react.useMemo)(
|
|
124
|
+
() => ({
|
|
125
|
+
user,
|
|
126
|
+
role,
|
|
127
|
+
isAuthenticated,
|
|
128
|
+
isAuthChecked,
|
|
129
|
+
login,
|
|
130
|
+
logout,
|
|
131
|
+
setUser,
|
|
132
|
+
tokenKey
|
|
133
|
+
}),
|
|
134
|
+
[user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
|
|
137
135
|
);
|
|
136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value, children });
|
|
138
137
|
};
|
|
139
138
|
const useAuth = () => {
|
|
140
139
|
const context = (0, import_react.useContext)(AuthContext);
|
|
141
140
|
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
142
141
|
return context;
|
|
143
142
|
};
|
|
144
|
-
return {
|
|
145
|
-
AuthProvider,
|
|
146
|
-
useAuth,
|
|
147
|
-
tokenKey: option?.tokenKey ?? "access_token"
|
|
148
|
-
};
|
|
143
|
+
return { AuthProvider, useAuth };
|
|
149
144
|
}
|
|
150
145
|
|
|
151
146
|
// src/AuthGuard.tsx
|
|
@@ -158,18 +153,16 @@ var AuthGuard = ({
|
|
|
158
153
|
fallback = null
|
|
159
154
|
}) => {
|
|
160
155
|
const router = (0, import_navigation.useRouter)();
|
|
161
|
-
const
|
|
156
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
157
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
162
158
|
(0, import_react2.useEffect)(() => {
|
|
163
159
|
if (!isAuthChecked) return;
|
|
164
160
|
if (!isAuthenticated) {
|
|
165
161
|
router.replace(redirectTo);
|
|
166
162
|
}
|
|
167
|
-
}, [isAuthenticated, isAuthChecked
|
|
163
|
+
}, [isAuthenticated, isAuthChecked]);
|
|
168
164
|
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
|
|
169
|
-
if (!isAuthenticated)
|
|
170
|
-
router.replace(redirectTo);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
165
|
+
if (!isAuthenticated) return null;
|
|
173
166
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
174
167
|
};
|
|
175
168
|
var AuthGuard_default = AuthGuard;
|
|
@@ -185,18 +178,17 @@ var RoleGuard = ({
|
|
|
185
178
|
fallback = null
|
|
186
179
|
}) => {
|
|
187
180
|
const router = (0, import_navigation2.useRouter)();
|
|
188
|
-
const
|
|
181
|
+
const role = useAuthStore((s) => s.role);
|
|
182
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
183
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
189
184
|
(0, import_react3.useEffect)(() => {
|
|
190
|
-
if (!isAuthChecked) return;
|
|
185
|
+
if (!isAuthChecked || !isAuthenticated) return;
|
|
191
186
|
if (!role || !allowedRoles.includes(role)) {
|
|
192
187
|
router.replace(redirectTo);
|
|
193
188
|
}
|
|
194
|
-
}, [role,
|
|
189
|
+
}, [role, isAuthenticated, isAuthChecked]);
|
|
195
190
|
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
|
|
196
|
-
if (!isAuthenticated)
|
|
197
|
-
router.replace(redirectTo);
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
191
|
+
if (!isAuthenticated) return null;
|
|
200
192
|
if (!role || !allowedRoles.includes(role)) return null;
|
|
201
193
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
|
|
202
194
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/AuthProvider.tsx
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useCallback
|
|
10
|
+
} from "react";
|
|
5
11
|
import { configureTokenManager, useTokenManager } from "react-token-manager";
|
|
6
12
|
|
|
7
13
|
// store/useGuardStore.ts
|
|
@@ -38,87 +44,82 @@ function createAuthContext(option) {
|
|
|
38
44
|
const storage = option?.storage ?? "cookie";
|
|
39
45
|
const tokenKey = option?.tokenKey ?? "access_token";
|
|
40
46
|
const rolePath = option?.rolePath ?? "role";
|
|
47
|
+
const manager = useTokenManager();
|
|
48
|
+
const user = useAuthStore((s) => s.user);
|
|
49
|
+
const role = useAuthStore((s) => s.role);
|
|
50
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
51
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
52
|
+
const setUser = useAuthStore((s) => s.setUser);
|
|
53
|
+
const setRole = useAuthStore((s) => s.setRole);
|
|
54
|
+
const setAuth = useAuthStore((s) => s.setAuth);
|
|
55
|
+
const setAuthChecked = useAuthStore((s) => s.setAuthChecked);
|
|
56
|
+
const resetAuth = useAuthStore((s) => s.resetAuth);
|
|
41
57
|
useEffect(() => {
|
|
42
58
|
configureTokenManager({ storage });
|
|
43
59
|
}, [storage]);
|
|
44
|
-
const manager = useTokenManager();
|
|
45
|
-
const {
|
|
46
|
-
user,
|
|
47
|
-
role,
|
|
48
|
-
setUser,
|
|
49
|
-
setRole,
|
|
50
|
-
resetAuth,
|
|
51
|
-
isAuthChecked,
|
|
52
|
-
isAuthenticated,
|
|
53
|
-
setAuth,
|
|
54
|
-
setAuthChecked
|
|
55
|
-
} = useAuthStore();
|
|
56
60
|
const extractRole = (userObj) => {
|
|
57
|
-
if (!userObj
|
|
61
|
+
if (!userObj) return null;
|
|
58
62
|
return rolePath.split(".").reduce((acc, key) => acc?.[key], userObj) ?? null;
|
|
59
63
|
};
|
|
60
64
|
useEffect(() => {
|
|
61
65
|
const storedUser = manager.getSingleToken("user");
|
|
62
66
|
const token = manager.getSingleToken(tokenKey);
|
|
63
67
|
if (token && !manager.isExpired(token)) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
setUser(
|
|
69
|
-
setRole(extractRole(
|
|
68
|
+
setAuth(true);
|
|
69
|
+
if (storedUser) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(storedUser);
|
|
72
|
+
setUser(parsed);
|
|
73
|
+
setRole(extractRole(parsed));
|
|
74
|
+
} catch {
|
|
75
|
+
resetAuth();
|
|
70
76
|
}
|
|
71
|
-
} catch {
|
|
72
|
-
resetAuth();
|
|
73
77
|
}
|
|
74
78
|
} else {
|
|
75
79
|
resetAuth();
|
|
76
80
|
}
|
|
77
81
|
setAuthChecked(true);
|
|
78
82
|
}, [tokenKey]);
|
|
79
|
-
const login = (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
const login = useCallback(
|
|
84
|
+
(tokens, userData, role2) => {
|
|
85
|
+
const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
|
|
86
|
+
manager.setTokens({
|
|
87
|
+
...tokens,
|
|
88
|
+
[tokenKey]: tokenValue,
|
|
89
|
+
user: JSON.stringify(userData ?? null)
|
|
90
|
+
});
|
|
91
|
+
if (userData) setUser(userData);
|
|
92
|
+
setRole(role2 ?? extractRole(userData));
|
|
93
|
+
setAuth(true);
|
|
94
|
+
setAuthChecked(true);
|
|
95
|
+
},
|
|
96
|
+
[tokenKey]
|
|
97
|
+
);
|
|
98
|
+
const logout = useCallback(() => {
|
|
92
99
|
manager.clearTokens();
|
|
93
100
|
resetAuth();
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
children
|
|
109
|
-
}
|
|
101
|
+
}, []);
|
|
102
|
+
const value = useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
user,
|
|
105
|
+
role,
|
|
106
|
+
isAuthenticated,
|
|
107
|
+
isAuthChecked,
|
|
108
|
+
login,
|
|
109
|
+
logout,
|
|
110
|
+
setUser,
|
|
111
|
+
tokenKey
|
|
112
|
+
}),
|
|
113
|
+
[user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
|
|
110
114
|
);
|
|
115
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
111
116
|
};
|
|
112
117
|
const useAuth = () => {
|
|
113
118
|
const context = useContext(AuthContext);
|
|
114
119
|
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
115
120
|
return context;
|
|
116
121
|
};
|
|
117
|
-
return {
|
|
118
|
-
AuthProvider,
|
|
119
|
-
useAuth,
|
|
120
|
-
tokenKey: option?.tokenKey ?? "access_token"
|
|
121
|
-
};
|
|
122
|
+
return { AuthProvider, useAuth };
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
// src/AuthGuard.tsx
|
|
@@ -131,18 +132,16 @@ var AuthGuard = ({
|
|
|
131
132
|
fallback = null
|
|
132
133
|
}) => {
|
|
133
134
|
const router = useRouter();
|
|
134
|
-
const
|
|
135
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
136
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
135
137
|
useEffect2(() => {
|
|
136
138
|
if (!isAuthChecked) return;
|
|
137
139
|
if (!isAuthenticated) {
|
|
138
140
|
router.replace(redirectTo);
|
|
139
141
|
}
|
|
140
|
-
}, [isAuthenticated, isAuthChecked
|
|
142
|
+
}, [isAuthenticated, isAuthChecked]);
|
|
141
143
|
if (!isAuthChecked) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
142
|
-
if (!isAuthenticated)
|
|
143
|
-
router.replace(redirectTo);
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
144
|
+
if (!isAuthenticated) return null;
|
|
146
145
|
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
147
146
|
};
|
|
148
147
|
var AuthGuard_default = AuthGuard;
|
|
@@ -158,18 +157,17 @@ var RoleGuard = ({
|
|
|
158
157
|
fallback = null
|
|
159
158
|
}) => {
|
|
160
159
|
const router = useRouter2();
|
|
161
|
-
const
|
|
160
|
+
const role = useAuthStore((s) => s.role);
|
|
161
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
162
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
162
163
|
useEffect3(() => {
|
|
163
|
-
if (!isAuthChecked) return;
|
|
164
|
+
if (!isAuthChecked || !isAuthenticated) return;
|
|
164
165
|
if (!role || !allowedRoles.includes(role)) {
|
|
165
166
|
router.replace(redirectTo);
|
|
166
167
|
}
|
|
167
|
-
}, [role,
|
|
168
|
+
}, [role, isAuthenticated, isAuthChecked]);
|
|
168
169
|
if (!isAuthChecked) return /* @__PURE__ */ jsx3(Fragment2, { children: fallback });
|
|
169
|
-
if (!isAuthenticated)
|
|
170
|
-
router.replace(redirectTo);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
170
|
+
if (!isAuthenticated) return null;
|
|
173
171
|
if (!role || !allowedRoles.includes(role)) return null;
|
|
174
172
|
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
175
173
|
};
|
package/package.json
CHANGED
package/readme.md
CHANGED
package/src/AuthGuard.tsx
CHANGED
|
@@ -1,41 +1,34 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
5
|
import { useAuthStore } from '../store/useGuardStore'
|
|
6
6
|
|
|
7
|
-
type AuthGuardProps = {
|
|
8
|
-
children: React.ReactNode
|
|
9
|
-
redirectTo?: string
|
|
10
|
-
fallback?: React.ReactNode
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
const AuthGuard = ({
|
|
14
8
|
children,
|
|
15
9
|
redirectTo = '/login',
|
|
16
10
|
fallback = null,
|
|
17
|
-
}:
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
redirectTo?: string
|
|
14
|
+
fallback?: React.ReactNode
|
|
15
|
+
}) => {
|
|
18
16
|
const router = useRouter()
|
|
19
17
|
|
|
20
|
-
const
|
|
18
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
19
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
|
|
21
20
|
|
|
22
21
|
useEffect(() => {
|
|
23
22
|
if (!isAuthChecked) return
|
|
24
|
-
|
|
25
23
|
if (!isAuthenticated) {
|
|
26
24
|
router.replace(redirectTo)
|
|
27
25
|
}
|
|
28
|
-
}, [isAuthenticated, isAuthChecked
|
|
26
|
+
}, [isAuthenticated, isAuthChecked])
|
|
29
27
|
|
|
30
28
|
if (!isAuthChecked) return <>{fallback}</>
|
|
31
|
-
|
|
32
|
-
if (!isAuthenticated) {
|
|
33
|
-
router.replace(redirectTo)
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
29
|
+
if (!isAuthenticated) return null
|
|
37
30
|
|
|
38
31
|
return <>{children}</>
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
export default AuthGuard
|
|
34
|
+
export default AuthGuard
|
package/src/AuthProvider.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useCallback,
|
|
10
|
+
} from 'react'
|
|
4
11
|
import { configureTokenManager, useTokenManager } from 'react-token-manager'
|
|
5
12
|
import { useAuthStore, User } from '../store/useGuardStore'
|
|
6
13
|
|
|
7
|
-
/* ---------------------------------- */
|
|
8
|
-
/* Types */
|
|
9
|
-
/* ---------------------------------- */
|
|
10
|
-
|
|
11
14
|
export type AuthContextType<UserType extends User = User> = {
|
|
12
15
|
user: UserType | null
|
|
13
16
|
role: string | null
|
|
@@ -19,14 +22,10 @@ export type AuthContextType<UserType extends User = User> = {
|
|
|
19
22
|
tokenKey: string
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
/* ---------------------------------- */
|
|
23
|
-
/* Factory */
|
|
24
|
-
/* ---------------------------------- */
|
|
25
|
-
|
|
26
25
|
export function createAuthContext<UserType extends User = User>(option?: {
|
|
27
26
|
storage?: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
28
27
|
tokenKey?: string
|
|
29
|
-
rolePath?: string
|
|
28
|
+
rolePath?: string
|
|
30
29
|
}) {
|
|
31
30
|
const AuthContext = createContext<AuthContextType<UserType> | undefined>(undefined)
|
|
32
31
|
|
|
@@ -35,109 +34,94 @@ export function createAuthContext<UserType extends User = User>(option?: {
|
|
|
35
34
|
const tokenKey = option?.tokenKey ?? 'access_token'
|
|
36
35
|
const rolePath = option?.rolePath ?? 'role'
|
|
37
36
|
|
|
37
|
+
const manager = useTokenManager()
|
|
38
|
+
|
|
39
|
+
// ✅ Selectors (IMPORTANT)
|
|
40
|
+
const user = useAuthStore((s) => s.user)
|
|
41
|
+
const role = useAuthStore((s) => s.role)
|
|
42
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
43
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
|
|
44
|
+
|
|
45
|
+
const setUser = useAuthStore((s) => s.setUser)
|
|
46
|
+
const setRole = useAuthStore((s) => s.setRole)
|
|
47
|
+
const setAuth = useAuthStore((s) => s.setAuth)
|
|
48
|
+
const setAuthChecked = useAuthStore((s) => s.setAuthChecked)
|
|
49
|
+
const resetAuth = useAuthStore((s) => s.resetAuth)
|
|
50
|
+
|
|
38
51
|
useEffect(() => {
|
|
39
52
|
configureTokenManager({ storage })
|
|
40
53
|
}, [storage])
|
|
41
54
|
|
|
42
|
-
const manager = useTokenManager()
|
|
43
|
-
const {
|
|
44
|
-
user,
|
|
45
|
-
role,
|
|
46
|
-
setUser,
|
|
47
|
-
setRole,
|
|
48
|
-
resetAuth,
|
|
49
|
-
isAuthChecked,
|
|
50
|
-
isAuthenticated,
|
|
51
|
-
setAuth,
|
|
52
|
-
setAuthChecked,
|
|
53
|
-
} = useAuthStore()
|
|
54
|
-
|
|
55
|
-
/* ---------------------------------- */
|
|
56
|
-
/* Helper: get role from path */
|
|
57
|
-
/* ---------------------------------- */
|
|
58
55
|
const extractRole = (userObj: any) => {
|
|
59
|
-
if (!userObj
|
|
56
|
+
if (!userObj) return null
|
|
60
57
|
return rolePath.split('.').reduce((acc: any, key: string) => acc?.[key], userObj) ?? null
|
|
61
58
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/* ---------------------------------- */
|
|
65
|
-
/* Hydrate user from storage */
|
|
66
|
-
/* ---------------------------------- */
|
|
67
59
|
|
|
60
|
+
// ✅ Hydrate once
|
|
68
61
|
useEffect(() => {
|
|
69
62
|
const storedUser = manager.getSingleToken('user')
|
|
70
63
|
const token = manager.getSingleToken(tokenKey)
|
|
71
64
|
|
|
72
65
|
if (token && !manager.isExpired(token)) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
setUser(
|
|
79
|
-
setRole(extractRole(
|
|
66
|
+
setAuth(true)
|
|
67
|
+
|
|
68
|
+
if (storedUser) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(storedUser)
|
|
71
|
+
setUser(parsed)
|
|
72
|
+
setRole(extractRole(parsed))
|
|
73
|
+
} catch {
|
|
74
|
+
resetAuth()
|
|
80
75
|
}
|
|
81
|
-
} catch {
|
|
82
|
-
resetAuth()
|
|
83
76
|
}
|
|
84
77
|
} else {
|
|
85
78
|
resetAuth()
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
setAuthChecked(true)
|
|
89
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
82
|
}, [tokenKey])
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/* ---------------------------------- */
|
|
112
|
-
/* Logout */
|
|
113
|
-
/* ---------------------------------- */
|
|
84
|
+
const login = useCallback(
|
|
85
|
+
(tokens: Record<string, string>, userData?: UserType, role?: string) => {
|
|
86
|
+
const tokenValue =
|
|
87
|
+
tokens[tokenKey] ?? tokens['access_token'] ?? Object.values(tokens)[0]
|
|
88
|
+
|
|
89
|
+
manager.setTokens({
|
|
90
|
+
...tokens,
|
|
91
|
+
[tokenKey]: tokenValue,
|
|
92
|
+
user: JSON.stringify(userData ?? null),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (userData) setUser(userData)
|
|
96
|
+
setRole(role ?? extractRole(userData))
|
|
97
|
+
setAuth(true)
|
|
98
|
+
setAuthChecked(true)
|
|
99
|
+
},
|
|
100
|
+
[tokenKey]
|
|
101
|
+
)
|
|
114
102
|
|
|
115
|
-
const logout = () => {
|
|
103
|
+
const logout = useCallback(() => {
|
|
116
104
|
manager.clearTokens()
|
|
117
105
|
resetAuth()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{children}
|
|
134
|
-
</AuthContext.Provider>
|
|
106
|
+
}, [])
|
|
107
|
+
|
|
108
|
+
// ✅ MEMOIZED CONTEXT VALUE
|
|
109
|
+
const value = useMemo(
|
|
110
|
+
() => ({
|
|
111
|
+
user: user as UserType | null,
|
|
112
|
+
role,
|
|
113
|
+
isAuthenticated,
|
|
114
|
+
isAuthChecked,
|
|
115
|
+
login,
|
|
116
|
+
logout,
|
|
117
|
+
setUser,
|
|
118
|
+
tokenKey,
|
|
119
|
+
}),
|
|
120
|
+
[user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
|
|
135
121
|
)
|
|
136
|
-
}
|
|
137
122
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/* ---------------------------------- */
|
|
123
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
124
|
+
}
|
|
141
125
|
|
|
142
126
|
const useAuth = () => {
|
|
143
127
|
const context = useContext(AuthContext)
|
|
@@ -145,9 +129,5 @@ export function createAuthContext<UserType extends User = User>(option?: {
|
|
|
145
129
|
return context
|
|
146
130
|
}
|
|
147
131
|
|
|
148
|
-
return {
|
|
149
|
-
|
|
150
|
-
useAuth,
|
|
151
|
-
tokenKey: option?.tokenKey ?? 'access_token',
|
|
152
|
-
}
|
|
153
|
-
}
|
|
132
|
+
return { AuthProvider, useAuth }
|
|
133
|
+
}
|
package/src/RoleGuard.tsx
CHANGED
|
@@ -1,45 +1,39 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
5
|
import { useAuthStore } from '../store/useGuardStore'
|
|
6
6
|
|
|
7
|
-
type RoleGuardProps = {
|
|
8
|
-
children: React.ReactNode
|
|
9
|
-
allowedRoles: string[]
|
|
10
|
-
redirectTo?: string
|
|
11
|
-
fallback?: React.ReactNode
|
|
12
|
-
}
|
|
13
|
-
|
|
14
7
|
const RoleGuard = ({
|
|
15
8
|
children,
|
|
16
9
|
allowedRoles,
|
|
17
10
|
redirectTo = '/unauthorized',
|
|
18
11
|
fallback = null,
|
|
19
|
-
}:
|
|
12
|
+
}: {
|
|
13
|
+
children: React.ReactNode
|
|
14
|
+
allowedRoles: string[]
|
|
15
|
+
redirectTo?: string
|
|
16
|
+
fallback?: React.ReactNode
|
|
17
|
+
}) => {
|
|
20
18
|
const router = useRouter()
|
|
21
|
-
|
|
19
|
+
|
|
20
|
+
const role = useAuthStore((s) => s.role)
|
|
21
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
22
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
|
|
22
23
|
|
|
23
24
|
useEffect(() => {
|
|
24
|
-
if (!isAuthChecked) return
|
|
25
|
+
if (!isAuthChecked || !isAuthenticated) return
|
|
25
26
|
|
|
26
|
-
// If role not allowed, redirect
|
|
27
27
|
if (!role || !allowedRoles.includes(role)) {
|
|
28
28
|
router.replace(redirectTo)
|
|
29
29
|
}
|
|
30
|
-
}, [role,
|
|
30
|
+
}, [role, isAuthenticated, isAuthChecked])
|
|
31
31
|
|
|
32
32
|
if (!isAuthChecked) return <>{fallback}</>
|
|
33
|
-
|
|
34
|
-
if (!isAuthenticated) {
|
|
35
|
-
router.replace(redirectTo)
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Block rendering if role is not allowed
|
|
33
|
+
if (!isAuthenticated) return null
|
|
40
34
|
if (!role || !allowedRoles.includes(role)) return null
|
|
41
35
|
|
|
42
36
|
return <>{children}</>
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
export default RoleGuard
|
|
39
|
+
export default RoleGuard
|