nextauthz 1.3.33 → 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 -71
- package/dist/index.mjs +67 -72
- package/package.json +1 -1
- package/readme.md +155 -73
- package/src/AuthGuard.tsx +11 -18
- package/src/AuthProvider.tsx +72 -95
- 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,90 +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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
setRole(extractRole(parsedUser));
|
|
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();
|
|
100
97
|
}
|
|
101
|
-
} catch {
|
|
102
|
-
resetAuth();
|
|
103
98
|
}
|
|
104
99
|
} else {
|
|
105
100
|
resetAuth();
|
|
106
101
|
}
|
|
107
102
|
setAuthChecked(true);
|
|
108
103
|
}, [tokenKey]);
|
|
109
|
-
const login = (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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)(() => {
|
|
122
120
|
manager.clearTokens();
|
|
123
121
|
resetAuth();
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
children
|
|
139
|
-
}
|
|
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]
|
|
140
135
|
);
|
|
136
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value, children });
|
|
141
137
|
};
|
|
142
138
|
const useAuth = () => {
|
|
143
139
|
const context = (0, import_react.useContext)(AuthContext);
|
|
144
140
|
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
145
141
|
return context;
|
|
146
142
|
};
|
|
147
|
-
return {
|
|
148
|
-
AuthProvider,
|
|
149
|
-
useAuth,
|
|
150
|
-
tokenKey: option?.tokenKey ?? "access_token"
|
|
151
|
-
};
|
|
143
|
+
return { AuthProvider, useAuth };
|
|
152
144
|
}
|
|
153
145
|
|
|
154
146
|
// src/AuthGuard.tsx
|
|
@@ -161,18 +153,16 @@ var AuthGuard = ({
|
|
|
161
153
|
fallback = null
|
|
162
154
|
}) => {
|
|
163
155
|
const router = (0, import_navigation.useRouter)();
|
|
164
|
-
const
|
|
156
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
157
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
165
158
|
(0, import_react2.useEffect)(() => {
|
|
166
159
|
if (!isAuthChecked) return;
|
|
167
160
|
if (!isAuthenticated) {
|
|
168
161
|
router.replace(redirectTo);
|
|
169
162
|
}
|
|
170
|
-
}, [isAuthenticated, isAuthChecked
|
|
163
|
+
}, [isAuthenticated, isAuthChecked]);
|
|
171
164
|
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
|
|
172
|
-
if (!isAuthenticated)
|
|
173
|
-
router.replace(redirectTo);
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
165
|
+
if (!isAuthenticated) return null;
|
|
176
166
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
177
167
|
};
|
|
178
168
|
var AuthGuard_default = AuthGuard;
|
|
@@ -188,18 +178,17 @@ var RoleGuard = ({
|
|
|
188
178
|
fallback = null
|
|
189
179
|
}) => {
|
|
190
180
|
const router = (0, import_navigation2.useRouter)();
|
|
191
|
-
const
|
|
181
|
+
const role = useAuthStore((s) => s.role);
|
|
182
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
183
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
192
184
|
(0, import_react3.useEffect)(() => {
|
|
193
|
-
if (!isAuthChecked) return;
|
|
185
|
+
if (!isAuthChecked || !isAuthenticated) return;
|
|
194
186
|
if (!role || !allowedRoles.includes(role)) {
|
|
195
187
|
router.replace(redirectTo);
|
|
196
188
|
}
|
|
197
|
-
}, [role,
|
|
189
|
+
}, [role, isAuthenticated, isAuthChecked]);
|
|
198
190
|
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
|
|
199
|
-
if (!isAuthenticated)
|
|
200
|
-
router.replace(redirectTo);
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
191
|
+
if (!isAuthenticated) return null;
|
|
203
192
|
if (!role || !allowedRoles.includes(role)) return null;
|
|
204
193
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
|
|
205
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,90 +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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
setRole(extractRole(parsedUser));
|
|
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();
|
|
73
76
|
}
|
|
74
|
-
} catch {
|
|
75
|
-
resetAuth();
|
|
76
77
|
}
|
|
77
78
|
} else {
|
|
78
79
|
resetAuth();
|
|
79
80
|
}
|
|
80
81
|
setAuthChecked(true);
|
|
81
82
|
}, [tokenKey]);
|
|
82
|
-
const login = (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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(() => {
|
|
95
99
|
manager.clearTokens();
|
|
96
100
|
resetAuth();
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
children
|
|
112
|
-
}
|
|
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]
|
|
113
114
|
);
|
|
115
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
114
116
|
};
|
|
115
117
|
const useAuth = () => {
|
|
116
118
|
const context = useContext(AuthContext);
|
|
117
119
|
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
118
120
|
return context;
|
|
119
121
|
};
|
|
120
|
-
return {
|
|
121
|
-
AuthProvider,
|
|
122
|
-
useAuth,
|
|
123
|
-
tokenKey: option?.tokenKey ?? "access_token"
|
|
124
|
-
};
|
|
122
|
+
return { AuthProvider, useAuth };
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
// src/AuthGuard.tsx
|
|
@@ -134,18 +132,16 @@ var AuthGuard = ({
|
|
|
134
132
|
fallback = null
|
|
135
133
|
}) => {
|
|
136
134
|
const router = useRouter();
|
|
137
|
-
const
|
|
135
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
136
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
138
137
|
useEffect2(() => {
|
|
139
138
|
if (!isAuthChecked) return;
|
|
140
139
|
if (!isAuthenticated) {
|
|
141
140
|
router.replace(redirectTo);
|
|
142
141
|
}
|
|
143
|
-
}, [isAuthenticated, isAuthChecked
|
|
142
|
+
}, [isAuthenticated, isAuthChecked]);
|
|
144
143
|
if (!isAuthChecked) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
145
|
-
if (!isAuthenticated)
|
|
146
|
-
router.replace(redirectTo);
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
144
|
+
if (!isAuthenticated) return null;
|
|
149
145
|
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
150
146
|
};
|
|
151
147
|
var AuthGuard_default = AuthGuard;
|
|
@@ -161,18 +157,17 @@ var RoleGuard = ({
|
|
|
161
157
|
fallback = null
|
|
162
158
|
}) => {
|
|
163
159
|
const router = useRouter2();
|
|
164
|
-
const
|
|
160
|
+
const role = useAuthStore((s) => s.role);
|
|
161
|
+
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
|
162
|
+
const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
|
|
165
163
|
useEffect3(() => {
|
|
166
|
-
if (!isAuthChecked) return;
|
|
164
|
+
if (!isAuthChecked || !isAuthenticated) return;
|
|
167
165
|
if (!role || !allowedRoles.includes(role)) {
|
|
168
166
|
router.replace(redirectTo);
|
|
169
167
|
}
|
|
170
|
-
}, [role,
|
|
168
|
+
}, [role, isAuthenticated, isAuthChecked]);
|
|
171
169
|
if (!isAuthChecked) return /* @__PURE__ */ jsx3(Fragment2, { children: fallback });
|
|
172
|
-
if (!isAuthenticated)
|
|
173
|
-
router.replace(redirectTo);
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
170
|
+
if (!isAuthenticated) return null;
|
|
176
171
|
if (!role || !allowedRoles.includes(role)) return null;
|
|
177
172
|
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
178
173
|
};
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -2,111 +2,158 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
** NextAuthZ provides:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
** Global auth state
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
** Token persistence
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
** Typed AuthProvider & useAuth
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
** Route protection (AuthGuard)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
** Role-based protection (RoleGuard)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
** Configurable storage system
|
|
18
|
+
|
|
19
|
+
** Configurable role extraction (rolePath)
|
|
20
|
+
|
|
21
|
+
Works perfectly with Next.js App Router.
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
21
25
|
npm install nextauthz
|
|
22
26
|
|
|
27
|
+
## Setup
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
1️⃣ Create Your Auth Instance
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Wrap your application with AuthProvider to provide global auth state (user, loading, error) and helper functions (login, logout, setUser).
|
|
31
|
+
Because NextAuthZ uses a factory pattern, you must create your own auth instance.
|
|
29
32
|
|
|
30
33
|
```bash
|
|
31
|
-
|
|
34
|
+
// src/lib/auth.ts
|
|
35
|
+
import { createAuthContext } from 'nextauthz'
|
|
36
|
+
|
|
37
|
+
export const {
|
|
38
|
+
AuthProvider,
|
|
39
|
+
useAuth,
|
|
40
|
+
} = createAuthContext({
|
|
41
|
+
storage: 'cookie', // 'localStorage' | 'sessionStorage' | 'cookie'
|
|
42
|
+
tokenKey: 'access_token', // optional (default: 'access_token')
|
|
43
|
+
rolePath: 'account_type', // optional (default: 'role')
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Available Options
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
| ---------- | ------------------------------------------------ | ---------------- | ------------------------------------- |
|
|
51
|
+
| `storage` | `'localStorage' \| 'sessionStorage' \| 'cookie'` | `'cookie'` | Where tokens are stored |
|
|
52
|
+
| `tokenKey` | `string` | `'access_token'` | Key used for primary token |
|
|
53
|
+
| `rolePath` | `string` | `'role'` | Path to extract role from user object |
|
|
35
54
|
|
|
36
55
|
|
|
37
|
-
// Options: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
38
|
-
// Defaults to 'cookie' if no storage is passed
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
|
|
58
|
+
## 2️⃣ Wrap Your App
|
|
59
|
+
|
|
60
|
+
Wrap your application with AuthProvider to provide global auth state (user, loading, error) and helper functions (login, logout, setUser).
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
// app/layout.tsx
|
|
64
|
+
|
|
65
|
+
import { AuthProvider } from '@/lib/auth'
|
|
66
|
+
|
|
67
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
41
68
|
return <AuthProvider>{children}</AuthProvider>
|
|
42
69
|
}
|
|
43
|
-
|
|
44
|
-
export default App
|
|
45
70
|
```
|
|
46
71
|
|
|
47
|
-
## Hook
|
|
72
|
+
## useAuth Hook
|
|
48
73
|
|
|
49
74
|
```bash
|
|
50
|
-
import { useAuth } from '
|
|
51
|
-
|
|
52
|
-
const {
|
|
75
|
+
import { useAuth } from '@/lib/auth'
|
|
76
|
+
|
|
77
|
+
const {
|
|
78
|
+
user,
|
|
79
|
+
role,
|
|
80
|
+
isAuthenticated,
|
|
81
|
+
isAuthChecked,
|
|
82
|
+
login,
|
|
83
|
+
logout,
|
|
84
|
+
setUser,
|
|
85
|
+
} = useAuth()
|
|
53
86
|
```
|
|
54
87
|
|
|
55
88
|
Available Properties
|
|
56
89
|
|
|
57
|
-
| Name | Type | Description
|
|
58
|
-
| ----------------- | ------------------------------------ |
|
|
59
|
-
| `user` | `User \| null` | Current authenticated user
|
|
60
|
-
| `role` | `string \| null` |
|
|
61
|
-
| `isAuthenticated` | `boolean` | True if
|
|
62
|
-
| `isAuthChecked` | `boolean` | True
|
|
63
|
-
| `login` | `(tokens, userData?, role?) => void` |
|
|
64
|
-
| `logout` | `() => void` | Clears
|
|
65
|
-
| `setUser` | `(user
|
|
90
|
+
| Name | Type | Description |
|
|
91
|
+
| ----------------- | ------------------------------------ | ---------------------------------------- |
|
|
92
|
+
| `user` | `User \| null` | Current authenticated user |
|
|
93
|
+
| `role` | `string \| null` | Extracted user role |
|
|
94
|
+
| `isAuthenticated` | `boolean` | True if logged in |
|
|
95
|
+
| `isAuthChecked` | `boolean` | True when auth hydration is complete |
|
|
96
|
+
| `login` | `(tokens, userData?, role?) => void` | Save tokens + optionally set user + role |
|
|
97
|
+
| `logout` | `() => void` | Clears tokens and resets auth |
|
|
98
|
+
| `setUser` | `(user \| null) => void` | Manually update user |
|
|
66
99
|
|
|
67
|
-
|
|
68
|
-
Example: Logging in
|
|
100
|
+
Example: Logging In
|
|
69
101
|
|
|
70
102
|
```bash
|
|
71
|
-
const
|
|
103
|
+
const { login } = useAuth()
|
|
104
|
+
|
|
105
|
+
const handleLogin = async () => {
|
|
72
106
|
const tokens = {
|
|
73
107
|
access_token: 'abc123',
|
|
74
108
|
refresh_token: 'xyz789',
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
const user = {
|
|
78
|
-
id: 1,
|
|
112
|
+
id: '1',
|
|
79
113
|
name: 'John',
|
|
80
|
-
|
|
114
|
+
account_type: 'admin',
|
|
81
115
|
}
|
|
82
116
|
|
|
83
|
-
login(tokens, user
|
|
117
|
+
login(tokens, user)
|
|
84
118
|
}
|
|
119
|
+
```
|
|
85
120
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
121
|
+
If rolePath: 'account_type' is configured, role is automatically extracted.
|
|
122
|
+
|
|
123
|
+
You can also override it manually:
|
|
124
|
+
|
|
125
|
+
``` bash
|
|
126
|
+
login(tokens, user, 'admin')
|
|
127
|
+
```
|
|
128
|
+
## Logging Out
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
const { logout } = useAuth()
|
|
132
|
+
|
|
133
|
+
const handleLogin = async () => {
|
|
134
|
+
logout()
|
|
135
|
+
router.replace('/authentication/login');
|
|
136
|
+
}
|
|
90
137
|
```
|
|
91
138
|
|
|
92
139
|
## AuthGuard
|
|
93
140
|
|
|
94
141
|
Purpose
|
|
95
142
|
|
|
96
|
-
Protect
|
|
143
|
+
Protect pages so only authenticated users can access them.
|
|
97
144
|
|
|
98
145
|
Props
|
|
99
146
|
|
|
100
|
-
| Name | Type | Default | Description
|
|
101
|
-
| ------------ | ----------- | -------- |
|
|
102
|
-
| `children` | `ReactNode` | required |
|
|
103
|
-
| `redirectTo` | `string` | `/login` |
|
|
104
|
-
| `fallback` | `ReactNode` | `null` | Optional loading
|
|
147
|
+
| Name | Type | Default | Description |
|
|
148
|
+
| ------------ | ----------- | -------- | ---------------------------------- |
|
|
149
|
+
| `children` | `ReactNode` | required | Content to render if authenticated |
|
|
150
|
+
| `redirectTo` | `string` | `/login` | Redirect if not authenticated |
|
|
151
|
+
| `fallback` | `ReactNode` | `null` | Optional loading UI |
|
|
105
152
|
|
|
106
153
|
## Usage Example
|
|
107
154
|
|
|
108
155
|
```bash
|
|
109
|
-
import AuthGuard from 'nextauthz'
|
|
156
|
+
import { AuthGuard } from 'nextauthz'
|
|
110
157
|
|
|
111
158
|
function DashboardPage() {
|
|
112
159
|
return (
|
|
@@ -118,36 +165,34 @@ function DashboardPage() {
|
|
|
118
165
|
```
|
|
119
166
|
Behavior:
|
|
120
167
|
|
|
121
|
-
**
|
|
168
|
+
** Waits for isAuthChecked
|
|
169
|
+
** Redirects to redirectTo value if not authenticated
|
|
170
|
+
** Renders children if authenticated
|
|
122
171
|
|
|
123
172
|
|
|
124
173
|
## RoleGuard
|
|
125
174
|
|
|
126
|
-
|
|
175
|
+
Restrict access based on role.
|
|
127
176
|
|
|
128
|
-
|
|
177
|
+
Props
|
|
129
178
|
|
|
130
|
-
| Name | Type | Default | Description
|
|
131
|
-
| -------------- | ----------- | --------------- |
|
|
132
|
-
| `children` | `ReactNode` | required |
|
|
133
|
-
| `allowedRoles` | `string[]` | required |
|
|
134
|
-
| `redirectTo` | `string` | `/unauthorized` | Redirect
|
|
135
|
-
| `fallback` | `ReactNode` | `null` | Optional loading
|
|
179
|
+
| Name | Type | Default | Description |
|
|
180
|
+
| -------------- | ----------- | --------------- | ---------------------------- |
|
|
181
|
+
| `children` | `ReactNode` | required | Content to render |
|
|
182
|
+
| `allowedRoles` | `string[]` | required | Allowed roles |
|
|
183
|
+
| `redirectTo` | `string` | `/unauthorized` | Redirect if role not allowed |
|
|
184
|
+
| `fallback` | `ReactNode` | `null` | Optional loading UI |
|
|
136
185
|
|
|
137
186
|
|
|
138
187
|
Usage Example
|
|
139
188
|
|
|
140
189
|
```bash
|
|
141
|
-
import RoleGuard from 'nextauthz'
|
|
142
|
-
import { useAuth } from 'nextauthz'
|
|
143
|
-
|
|
144
|
-
function AdminPage() {
|
|
145
|
-
const { user } = useAuth()
|
|
190
|
+
import { RoleGuard } from 'nextauthz'
|
|
146
191
|
|
|
192
|
+
export default function AdminPage() {
|
|
147
193
|
return (
|
|
148
|
-
<RoleGuard allowedRoles={['admin']} fallback={<p>Checking
|
|
194
|
+
<RoleGuard allowedRoles={['admin']} fallback={<p>Checking role...</p>}>
|
|
149
195
|
<h1>Admin Dashboard</h1>
|
|
150
|
-
<p>Welcome, {user?.name}</p>
|
|
151
196
|
</RoleGuard>
|
|
152
197
|
)
|
|
153
198
|
}
|
|
@@ -155,19 +200,52 @@ function AdminPage() {
|
|
|
155
200
|
|
|
156
201
|
Behavior:
|
|
157
202
|
|
|
158
|
-
**
|
|
159
|
-
**
|
|
203
|
+
** Waits for isAuthChecked
|
|
204
|
+
** Redirects if role not in allowedRoles
|
|
205
|
+
** Blocks rendering if unauthorized
|
|
160
206
|
|
|
161
207
|
|
|
162
208
|
## Storage Options
|
|
163
209
|
|
|
164
210
|
You can configure token storage:
|
|
165
211
|
|
|
166
|
-
| Storage Type |
|
|
167
|
-
| ---------------- |
|
|
168
|
-
| `localStorage` | Persists until
|
|
169
|
-
| `sessionStorage` | Clears on tab close
|
|
170
|
-
| `cookie` | Cookie-based
|
|
212
|
+
| Storage Type | Behavior |
|
|
213
|
+
| ---------------- | ---------------------- |
|
|
214
|
+
| `localStorage` | Persists until cleared |
|
|
215
|
+
| `sessionStorage` | Clears on tab close |
|
|
216
|
+
| `cookie` | Cookie-based (default) |
|
|
217
|
+
|
|
218
|
+
## Role Path Examples
|
|
219
|
+
Given this user:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
{
|
|
223
|
+
id: "1",
|
|
224
|
+
email: "user@email.com",
|
|
225
|
+
account_type: "artist"
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Use:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
rolePath: 'account_type'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Nested example:
|
|
236
|
+
```bash
|
|
237
|
+
{
|
|
238
|
+
profile: {
|
|
239
|
+
role: 'admin'
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Use:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
rolePath: 'profile.role'
|
|
248
|
+
```
|
|
171
249
|
|
|
172
250
|
|
|
173
251
|
## Why NextAuthZ?
|
|
@@ -184,4 +262,8 @@ You can configure token storage:
|
|
|
184
262
|
|
|
185
263
|
** Lightweight
|
|
186
264
|
|
|
187
|
-
** No opinionated backend
|
|
265
|
+
** No opinionated backend
|
|
266
|
+
|
|
267
|
+
** Backend agnostic
|
|
268
|
+
|
|
269
|
+
** Factory-based (multiple auth instances supported)
|
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,112 +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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setRole(extractRole(parsedUser))
|
|
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()
|
|
83
75
|
}
|
|
84
|
-
} catch {
|
|
85
|
-
resetAuth()
|
|
86
76
|
}
|
|
87
77
|
} else {
|
|
88
78
|
resetAuth()
|
|
89
79
|
}
|
|
90
80
|
|
|
91
81
|
setAuthChecked(true)
|
|
92
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
82
|
}, [tokenKey])
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/* ---------------------------------- */
|
|
115
|
-
/* Logout */
|
|
116
|
-
/* ---------------------------------- */
|
|
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
|
+
)
|
|
117
102
|
|
|
118
|
-
const logout = () => {
|
|
103
|
+
const logout = useCallback(() => {
|
|
119
104
|
manager.clearTokens()
|
|
120
105
|
resetAuth()
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{children}
|
|
137
|
-
</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]
|
|
138
121
|
)
|
|
139
|
-
}
|
|
140
122
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/* ---------------------------------- */
|
|
123
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
124
|
+
}
|
|
144
125
|
|
|
145
126
|
const useAuth = () => {
|
|
146
127
|
const context = useContext(AuthContext)
|
|
@@ -148,9 +129,5 @@ export function createAuthContext<UserType extends User = User>(option?: {
|
|
|
148
129
|
return context
|
|
149
130
|
}
|
|
150
131
|
|
|
151
|
-
return {
|
|
152
|
-
|
|
153
|
-
useAuth,
|
|
154
|
-
tokenKey: option?.tokenKey ?? 'access_token',
|
|
155
|
-
}
|
|
156
|
-
}
|
|
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
|