nextauthz 1.2.1 → 1.2.4
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 +16 -10
- package/dist/index.d.ts +16 -10
- package/dist/index.js +72 -71
- package/dist/index.mjs +74 -78
- package/package.json +1 -1
- package/readme.md +39 -38
- package/src/AuthGuard.tsx +11 -18
- package/src/AuthProvider.tsx +96 -64
- package/src/RoleGuard.tsx +10 -22
- package/src/index.ts +7 -3
- package/store/useGuardStore.ts +12 -3
package/dist/index.d.mts
CHANGED
|
@@ -6,35 +6,41 @@ type User$1 = {
|
|
|
6
6
|
[key: string]: any;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
type AuthContextType<UserType extends User$1> = {
|
|
9
|
+
type AuthContextType<UserType extends User$1 = User$1> = {
|
|
10
10
|
user: UserType | null;
|
|
11
|
-
|
|
11
|
+
role: string | null;
|
|
12
|
+
isAuthenticated: boolean;
|
|
13
|
+
login: (tokens: Record<string, string>, userData: UserType, role: string) => void;
|
|
12
14
|
logout: () => void;
|
|
13
|
-
setUser: (user: UserType) => void;
|
|
14
|
-
|
|
15
|
-
error: Error | null;
|
|
15
|
+
setUser: (user: UserType | null) => void;
|
|
16
|
+
tokenKey: string;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
type AuthGuardProps = {
|
|
19
20
|
children: React__default.ReactNode;
|
|
20
21
|
redirectTo?: string;
|
|
21
|
-
|
|
22
|
+
fallback?: React__default.ReactNode;
|
|
22
23
|
};
|
|
23
|
-
declare const AuthGuard: ({ children, redirectTo,
|
|
24
|
+
declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
24
25
|
|
|
25
26
|
type RoleGuardProps = {
|
|
26
27
|
children: React__default.ReactNode;
|
|
27
28
|
allowedRoles: string[];
|
|
28
29
|
redirectTo?: string;
|
|
30
|
+
fallback?: React__default.ReactNode;
|
|
29
31
|
};
|
|
30
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
32
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
31
33
|
|
|
32
|
-
type User = any
|
|
33
|
-
|
|
34
|
+
type User = Record<string, any>;
|
|
35
|
+
/**
|
|
36
|
+
* Factory to create isolated auth instance
|
|
37
|
+
*/
|
|
38
|
+
declare function createAppAuth(storage?: 'localStorage' | 'sessionStorage' | 'cookie', tokenKey?: string): {
|
|
34
39
|
AuthProvider: ({ children }: {
|
|
35
40
|
children: React.ReactNode;
|
|
36
41
|
}) => react_jsx_runtime.JSX.Element;
|
|
37
42
|
useAuth: () => AuthContextType<User$1>;
|
|
43
|
+
tokenKey: string;
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
export { AuthGuard, RoleGuard, type User, createAppAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,35 +6,41 @@ type User$1 = {
|
|
|
6
6
|
[key: string]: any;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
type AuthContextType<UserType extends User$1> = {
|
|
9
|
+
type AuthContextType<UserType extends User$1 = User$1> = {
|
|
10
10
|
user: UserType | null;
|
|
11
|
-
|
|
11
|
+
role: string | null;
|
|
12
|
+
isAuthenticated: boolean;
|
|
13
|
+
login: (tokens: Record<string, string>, userData: UserType, role: string) => void;
|
|
12
14
|
logout: () => void;
|
|
13
|
-
setUser: (user: UserType) => void;
|
|
14
|
-
|
|
15
|
-
error: Error | null;
|
|
15
|
+
setUser: (user: UserType | null) => void;
|
|
16
|
+
tokenKey: string;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
type AuthGuardProps = {
|
|
19
20
|
children: React__default.ReactNode;
|
|
20
21
|
redirectTo?: string;
|
|
21
|
-
|
|
22
|
+
fallback?: React__default.ReactNode;
|
|
22
23
|
};
|
|
23
|
-
declare const AuthGuard: ({ children, redirectTo,
|
|
24
|
+
declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
24
25
|
|
|
25
26
|
type RoleGuardProps = {
|
|
26
27
|
children: React__default.ReactNode;
|
|
27
28
|
allowedRoles: string[];
|
|
28
29
|
redirectTo?: string;
|
|
30
|
+
fallback?: React__default.ReactNode;
|
|
29
31
|
};
|
|
30
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
32
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
31
33
|
|
|
32
|
-
type User = any
|
|
33
|
-
|
|
34
|
+
type User = Record<string, any>;
|
|
35
|
+
/**
|
|
36
|
+
* Factory to create isolated auth instance
|
|
37
|
+
*/
|
|
38
|
+
declare function createAppAuth(storage?: 'localStorage' | 'sessionStorage' | 'cookie', tokenKey?: string): {
|
|
34
39
|
AuthProvider: ({ children }: {
|
|
35
40
|
children: React.ReactNode;
|
|
36
41
|
}) => react_jsx_runtime.JSX.Element;
|
|
37
42
|
useAuth: () => AuthContextType<User$1>;
|
|
43
|
+
tokenKey: string;
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
export { AuthGuard, RoleGuard, type User, createAppAuth };
|
package/dist/index.js
CHANGED
|
@@ -35,18 +35,20 @@ var import_react_token_manager = require("react-token-manager");
|
|
|
35
35
|
var import_zustand = require("zustand");
|
|
36
36
|
var useAuthStore = (0, import_zustand.create)((set) => ({
|
|
37
37
|
user: null,
|
|
38
|
+
role: null,
|
|
38
39
|
isAuthenticated: false,
|
|
39
40
|
isAuthChecked: false,
|
|
40
41
|
loading: true,
|
|
41
|
-
// start as loading
|
|
42
42
|
error: null,
|
|
43
43
|
setUser: (user) => set({ user }),
|
|
44
|
+
setRole: (role) => set({ role }),
|
|
44
45
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
45
46
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
46
47
|
setLoading: (loading) => set({ loading }),
|
|
47
48
|
setError: (err) => set({ error: err }),
|
|
48
49
|
resetAuth: () => set({
|
|
49
50
|
user: null,
|
|
51
|
+
role: null,
|
|
50
52
|
isAuthenticated: false,
|
|
51
53
|
isAuthChecked: false,
|
|
52
54
|
loading: false,
|
|
@@ -56,99 +58,104 @@ var useAuthStore = (0, import_zustand.create)((set) => ({
|
|
|
56
58
|
|
|
57
59
|
// src/AuthProvider.tsx
|
|
58
60
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
59
|
-
function createAuthContext(
|
|
60
|
-
const
|
|
61
|
-
const AuthContext = (0, import_react.createContext)(
|
|
62
|
-
void 0
|
|
63
|
-
);
|
|
61
|
+
function createAuthContext(option) {
|
|
62
|
+
const AuthContext = (0, import_react.createContext)(void 0);
|
|
64
63
|
const AuthProvider = ({ children }) => {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const { user, loading, error, setUser, setLoading, setError, resetAuth } = useAuthStore();
|
|
64
|
+
const storage = option?.storage ?? "cookie";
|
|
65
|
+
const tokenKey = option?.tokenKey ?? "access_token";
|
|
68
66
|
(0, import_react.useEffect)(() => {
|
|
69
|
-
|
|
70
|
-
(0, import_react_token_manager.configureTokenManager)({ storage });
|
|
71
|
-
configuredRef.current = true;
|
|
72
|
-
}
|
|
67
|
+
(0, import_react_token_manager.configureTokenManager)({ storage });
|
|
73
68
|
}, [storage]);
|
|
69
|
+
const manager = (0, import_react_token_manager.useTokenManager)();
|
|
70
|
+
const {
|
|
71
|
+
user,
|
|
72
|
+
role,
|
|
73
|
+
setUser,
|
|
74
|
+
setRole,
|
|
75
|
+
resetAuth,
|
|
76
|
+
isAuthenticated,
|
|
77
|
+
setAuth,
|
|
78
|
+
setAuthChecked
|
|
79
|
+
} = useAuthStore();
|
|
74
80
|
(0, import_react.useEffect)(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const storedUser = manager.getSingleToken("user");
|
|
82
|
+
const token = manager.getSingleToken(tokenKey);
|
|
83
|
+
if (storedUser && token && !manager.isExpired(token)) {
|
|
84
|
+
try {
|
|
85
|
+
const parsedUser = JSON.parse(storedUser);
|
|
86
|
+
setUser(parsedUser);
|
|
87
|
+
setRole(parsedUser?.role ?? null);
|
|
88
|
+
setAuth(true);
|
|
89
|
+
} catch {
|
|
90
|
+
resetAuth();
|
|
79
91
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
} finally {
|
|
83
|
-
setLoading(false);
|
|
84
|
-
}
|
|
85
|
-
}, [manager, setUser, setLoading, setError]);
|
|
86
|
-
const login = (tokens, userData) => {
|
|
87
|
-
try {
|
|
88
|
-
manager.setTokens({ ...tokens, user: JSON.stringify(userData) });
|
|
89
|
-
setUser(userData);
|
|
90
|
-
} catch (err) {
|
|
91
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
92
|
+
} else {
|
|
93
|
+
resetAuth();
|
|
92
94
|
}
|
|
95
|
+
setAuthChecked(true);
|
|
96
|
+
}, [tokenKey]);
|
|
97
|
+
const login = (tokens, userData, role2) => {
|
|
98
|
+
const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
|
|
99
|
+
manager.setTokens({
|
|
100
|
+
...tokens,
|
|
101
|
+
[tokenKey]: tokenValue,
|
|
102
|
+
user: JSON.stringify(userData)
|
|
103
|
+
});
|
|
104
|
+
setUser(userData);
|
|
105
|
+
setRole(role2);
|
|
106
|
+
setAuth(true);
|
|
93
107
|
};
|
|
94
108
|
const logout = () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
resetAuth();
|
|
98
|
-
} catch (err) {
|
|
99
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
100
|
-
}
|
|
109
|
+
manager.clearTokens();
|
|
110
|
+
resetAuth();
|
|
101
111
|
};
|
|
102
112
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
103
113
|
AuthContext.Provider,
|
|
104
114
|
{
|
|
105
115
|
value: {
|
|
106
116
|
user,
|
|
117
|
+
role,
|
|
118
|
+
isAuthenticated,
|
|
107
119
|
login,
|
|
108
120
|
logout,
|
|
109
121
|
setUser: (u) => setUser(u),
|
|
110
|
-
|
|
111
|
-
error
|
|
122
|
+
tokenKey
|
|
112
123
|
},
|
|
113
124
|
children
|
|
114
125
|
}
|
|
115
126
|
);
|
|
116
127
|
};
|
|
117
128
|
const useAuth = () => {
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
120
|
-
return
|
|
129
|
+
const context = (0, import_react.useContext)(AuthContext);
|
|
130
|
+
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
131
|
+
return context;
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
AuthProvider,
|
|
135
|
+
useAuth,
|
|
136
|
+
tokenKey: option?.tokenKey ?? "access_token"
|
|
121
137
|
};
|
|
122
|
-
return { AuthProvider, useAuth };
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
// src/AuthGuard.tsx
|
|
126
141
|
var import_react2 = require("react");
|
|
127
142
|
var import_navigation = require("next/navigation");
|
|
128
|
-
var import_react_token_manager2 = require("react-token-manager");
|
|
129
143
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
130
144
|
var AuthGuard = ({
|
|
131
145
|
children,
|
|
132
146
|
redirectTo = "/login",
|
|
133
|
-
|
|
147
|
+
fallback = null
|
|
134
148
|
}) => {
|
|
135
149
|
const router = (0, import_navigation.useRouter)();
|
|
136
|
-
const
|
|
137
|
-
const setAuth = useAuthStore((state) => state.setAuth);
|
|
138
|
-
const setAuthChecked = useAuthStore((state) => state.setAuthChecked);
|
|
139
|
-
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
150
|
+
const { isAuthenticated, isAuthChecked } = useAuthStore();
|
|
140
151
|
(0, import_react2.useEffect)(() => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
setAuth(Boolean(isValid));
|
|
144
|
-
setAuthChecked(true);
|
|
145
|
-
if (!isValid) {
|
|
152
|
+
if (!isAuthChecked) return;
|
|
153
|
+
if (!isAuthenticated) {
|
|
146
154
|
router.replace(redirectTo);
|
|
147
|
-
} else {
|
|
148
|
-
setLoading(false);
|
|
149
155
|
}
|
|
150
|
-
}, [
|
|
151
|
-
if (
|
|
156
|
+
}, [isAuthenticated, isAuthChecked, redirectTo, router]);
|
|
157
|
+
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
|
|
158
|
+
if (!isAuthenticated) return null;
|
|
152
159
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
153
160
|
};
|
|
154
161
|
var AuthGuard_default = AuthGuard;
|
|
@@ -160,32 +167,26 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
160
167
|
var RoleGuard = ({
|
|
161
168
|
children,
|
|
162
169
|
allowedRoles,
|
|
163
|
-
redirectTo = "/unauthorized"
|
|
170
|
+
redirectTo = "/unauthorized",
|
|
171
|
+
fallback = null
|
|
164
172
|
}) => {
|
|
165
173
|
const router = (0, import_navigation2.useRouter)();
|
|
166
|
-
const {
|
|
174
|
+
const { role, isAuthChecked } = useAuthStore();
|
|
167
175
|
(0, import_react3.useEffect)(() => {
|
|
168
176
|
if (!isAuthChecked) return;
|
|
169
|
-
if (!
|
|
170
|
-
router.replace("/login");
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const role2 = user?.role;
|
|
174
|
-
if (!allowedRoles.includes(role2)) {
|
|
177
|
+
if (!role || !allowedRoles.includes(role)) {
|
|
175
178
|
router.replace(redirectTo);
|
|
176
179
|
}
|
|
177
|
-
}, [
|
|
178
|
-
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
179
|
-
if (!
|
|
180
|
-
const role = user?.role;
|
|
181
|
-
if (!allowedRoles.includes(role)) return null;
|
|
180
|
+
}, [role, isAuthChecked, allowedRoles, redirectTo, router]);
|
|
181
|
+
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
|
|
182
|
+
if (!role || !allowedRoles.includes(role)) return null;
|
|
182
183
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
|
|
183
184
|
};
|
|
184
185
|
var RoleGuard_default = RoleGuard;
|
|
185
186
|
|
|
186
187
|
// src/index.ts
|
|
187
|
-
function createAppAuth(storage = "cookie") {
|
|
188
|
-
return createAuthContext({ storage });
|
|
188
|
+
function createAppAuth(storage = "cookie", tokenKey = "access_token") {
|
|
189
|
+
return createAuthContext({ storage, tokenKey });
|
|
189
190
|
}
|
|
190
191
|
// Annotate the CommonJS export names for ESM import in node:
|
|
191
192
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/AuthProvider.tsx
|
|
4
|
-
import {
|
|
5
|
-
createContext,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
useRef
|
|
9
|
-
} from "react";
|
|
4
|
+
import { createContext, useContext, useEffect } from "react";
|
|
10
5
|
import { configureTokenManager, useTokenManager } from "react-token-manager";
|
|
11
6
|
|
|
12
7
|
// store/useGuardStore.ts
|
|
13
8
|
import { create } from "zustand";
|
|
14
9
|
var useAuthStore = create((set) => ({
|
|
15
10
|
user: null,
|
|
11
|
+
role: null,
|
|
16
12
|
isAuthenticated: false,
|
|
17
13
|
isAuthChecked: false,
|
|
18
14
|
loading: true,
|
|
19
|
-
// start as loading
|
|
20
15
|
error: null,
|
|
21
16
|
setUser: (user) => set({ user }),
|
|
17
|
+
setRole: (role) => set({ role }),
|
|
22
18
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
23
19
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
24
20
|
setLoading: (loading) => set({ loading }),
|
|
25
21
|
setError: (err) => set({ error: err }),
|
|
26
22
|
resetAuth: () => set({
|
|
27
23
|
user: null,
|
|
24
|
+
role: null,
|
|
28
25
|
isAuthenticated: false,
|
|
29
26
|
isAuthChecked: false,
|
|
30
27
|
loading: false,
|
|
@@ -34,99 +31,104 @@ var useAuthStore = create((set) => ({
|
|
|
34
31
|
|
|
35
32
|
// src/AuthProvider.tsx
|
|
36
33
|
import { jsx } from "react/jsx-runtime";
|
|
37
|
-
function createAuthContext(
|
|
38
|
-
const
|
|
39
|
-
const AuthContext = createContext(
|
|
40
|
-
void 0
|
|
41
|
-
);
|
|
34
|
+
function createAuthContext(option) {
|
|
35
|
+
const AuthContext = createContext(void 0);
|
|
42
36
|
const AuthProvider = ({ children }) => {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const { user, loading, error, setUser, setLoading, setError, resetAuth } = useAuthStore();
|
|
37
|
+
const storage = option?.storage ?? "cookie";
|
|
38
|
+
const tokenKey = option?.tokenKey ?? "access_token";
|
|
46
39
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
configureTokenManager({ storage });
|
|
49
|
-
configuredRef.current = true;
|
|
50
|
-
}
|
|
40
|
+
configureTokenManager({ storage });
|
|
51
41
|
}, [storage]);
|
|
42
|
+
const manager = useTokenManager();
|
|
43
|
+
const {
|
|
44
|
+
user,
|
|
45
|
+
role,
|
|
46
|
+
setUser,
|
|
47
|
+
setRole,
|
|
48
|
+
resetAuth,
|
|
49
|
+
isAuthenticated,
|
|
50
|
+
setAuth,
|
|
51
|
+
setAuthChecked
|
|
52
|
+
} = useAuthStore();
|
|
52
53
|
useEffect(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const storedUser = manager.getSingleToken("user");
|
|
55
|
+
const token = manager.getSingleToken(tokenKey);
|
|
56
|
+
if (storedUser && token && !manager.isExpired(token)) {
|
|
57
|
+
try {
|
|
58
|
+
const parsedUser = JSON.parse(storedUser);
|
|
59
|
+
setUser(parsedUser);
|
|
60
|
+
setRole(parsedUser?.role ?? null);
|
|
61
|
+
setAuth(true);
|
|
62
|
+
} catch {
|
|
63
|
+
resetAuth();
|
|
57
64
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
} finally {
|
|
61
|
-
setLoading(false);
|
|
62
|
-
}
|
|
63
|
-
}, [manager, setUser, setLoading, setError]);
|
|
64
|
-
const login = (tokens, userData) => {
|
|
65
|
-
try {
|
|
66
|
-
manager.setTokens({ ...tokens, user: JSON.stringify(userData) });
|
|
67
|
-
setUser(userData);
|
|
68
|
-
} catch (err) {
|
|
69
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
65
|
+
} else {
|
|
66
|
+
resetAuth();
|
|
70
67
|
}
|
|
68
|
+
setAuthChecked(true);
|
|
69
|
+
}, [tokenKey]);
|
|
70
|
+
const login = (tokens, userData, role2) => {
|
|
71
|
+
const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
|
|
72
|
+
manager.setTokens({
|
|
73
|
+
...tokens,
|
|
74
|
+
[tokenKey]: tokenValue,
|
|
75
|
+
user: JSON.stringify(userData)
|
|
76
|
+
});
|
|
77
|
+
setUser(userData);
|
|
78
|
+
setRole(role2);
|
|
79
|
+
setAuth(true);
|
|
71
80
|
};
|
|
72
81
|
const logout = () => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
resetAuth();
|
|
76
|
-
} catch (err) {
|
|
77
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
78
|
-
}
|
|
82
|
+
manager.clearTokens();
|
|
83
|
+
resetAuth();
|
|
79
84
|
};
|
|
80
85
|
return /* @__PURE__ */ jsx(
|
|
81
86
|
AuthContext.Provider,
|
|
82
87
|
{
|
|
83
88
|
value: {
|
|
84
89
|
user,
|
|
90
|
+
role,
|
|
91
|
+
isAuthenticated,
|
|
85
92
|
login,
|
|
86
93
|
logout,
|
|
87
94
|
setUser: (u) => setUser(u),
|
|
88
|
-
|
|
89
|
-
error
|
|
95
|
+
tokenKey
|
|
90
96
|
},
|
|
91
97
|
children
|
|
92
98
|
}
|
|
93
99
|
);
|
|
94
100
|
};
|
|
95
101
|
const useAuth = () => {
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
98
|
-
return
|
|
102
|
+
const context = useContext(AuthContext);
|
|
103
|
+
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
|
104
|
+
return context;
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
AuthProvider,
|
|
108
|
+
useAuth,
|
|
109
|
+
tokenKey: option?.tokenKey ?? "access_token"
|
|
99
110
|
};
|
|
100
|
-
return { AuthProvider, useAuth };
|
|
101
111
|
}
|
|
102
112
|
|
|
103
113
|
// src/AuthGuard.tsx
|
|
104
|
-
import { useEffect as useEffect2
|
|
114
|
+
import { useEffect as useEffect2 } from "react";
|
|
105
115
|
import { useRouter } from "next/navigation";
|
|
106
|
-
import { useTokenManager as useTokenManager2 } from "react-token-manager";
|
|
107
116
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
108
117
|
var AuthGuard = ({
|
|
109
118
|
children,
|
|
110
119
|
redirectTo = "/login",
|
|
111
|
-
|
|
120
|
+
fallback = null
|
|
112
121
|
}) => {
|
|
113
122
|
const router = useRouter();
|
|
114
|
-
const
|
|
115
|
-
const setAuth = useAuthStore((state) => state.setAuth);
|
|
116
|
-
const setAuthChecked = useAuthStore((state) => state.setAuthChecked);
|
|
117
|
-
const [loading, setLoading] = useState(true);
|
|
123
|
+
const { isAuthenticated, isAuthChecked } = useAuthStore();
|
|
118
124
|
useEffect2(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
setAuth(Boolean(isValid));
|
|
122
|
-
setAuthChecked(true);
|
|
123
|
-
if (!isValid) {
|
|
125
|
+
if (!isAuthChecked) return;
|
|
126
|
+
if (!isAuthenticated) {
|
|
124
127
|
router.replace(redirectTo);
|
|
125
|
-
} else {
|
|
126
|
-
setLoading(false);
|
|
127
128
|
}
|
|
128
|
-
}, [
|
|
129
|
-
if (
|
|
129
|
+
}, [isAuthenticated, isAuthChecked, redirectTo, router]);
|
|
130
|
+
if (!isAuthChecked) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
131
|
+
if (!isAuthenticated) return null;
|
|
130
132
|
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
131
133
|
};
|
|
132
134
|
var AuthGuard_default = AuthGuard;
|
|
@@ -138,32 +140,26 @@ import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
|
|
|
138
140
|
var RoleGuard = ({
|
|
139
141
|
children,
|
|
140
142
|
allowedRoles,
|
|
141
|
-
redirectTo = "/unauthorized"
|
|
143
|
+
redirectTo = "/unauthorized",
|
|
144
|
+
fallback = null
|
|
142
145
|
}) => {
|
|
143
146
|
const router = useRouter2();
|
|
144
|
-
const {
|
|
147
|
+
const { role, isAuthChecked } = useAuthStore();
|
|
145
148
|
useEffect3(() => {
|
|
146
149
|
if (!isAuthChecked) return;
|
|
147
|
-
if (!
|
|
148
|
-
router.replace("/login");
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const role2 = user?.role;
|
|
152
|
-
if (!allowedRoles.includes(role2)) {
|
|
150
|
+
if (!role || !allowedRoles.includes(role)) {
|
|
153
151
|
router.replace(redirectTo);
|
|
154
152
|
}
|
|
155
|
-
}, [
|
|
156
|
-
if (!isAuthChecked) return /* @__PURE__ */ jsx3(
|
|
157
|
-
if (!
|
|
158
|
-
const role = user?.role;
|
|
159
|
-
if (!allowedRoles.includes(role)) return null;
|
|
153
|
+
}, [role, isAuthChecked, allowedRoles, redirectTo, router]);
|
|
154
|
+
if (!isAuthChecked) return /* @__PURE__ */ jsx3(Fragment2, { children: fallback });
|
|
155
|
+
if (!role || !allowedRoles.includes(role)) return null;
|
|
160
156
|
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
161
157
|
};
|
|
162
158
|
var RoleGuard_default = RoleGuard;
|
|
163
159
|
|
|
164
160
|
// src/index.ts
|
|
165
|
-
function createAppAuth(storage = "cookie") {
|
|
166
|
-
return createAuthContext({ storage });
|
|
161
|
+
function createAppAuth(storage = "cookie", tokenKey = "access_token") {
|
|
162
|
+
return createAuthContext({ storage, tokenKey });
|
|
167
163
|
}
|
|
168
164
|
export {
|
|
169
165
|
AuthGuard_default as AuthGuard,
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -31,21 +31,17 @@ Wrap your application with AuthProvider to provide global auth state (user, load
|
|
|
31
31
|
import { createAppAuth } from 'nextauthz'
|
|
32
32
|
|
|
33
33
|
// Choose storage type
|
|
34
|
-
const { AuthProvider } = createAppAuth('
|
|
35
|
-
// Options: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
36
|
-
|
|
34
|
+
const { AuthProvider, useAuth } = createAppAuth('cookie', 'access_token')
|
|
37
35
|
|
|
38
|
-
If no storage is passed, default is:
|
|
39
|
-
|
|
40
|
-
createAppAuth() // defaults to 'cookie'
|
|
41
36
|
|
|
37
|
+
// Options: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
38
|
+
// Defaults to 'cookie' if no storage is passed
|
|
42
39
|
|
|
43
40
|
function App({ children }: { children: React.ReactNode }) {
|
|
44
41
|
return <AuthProvider>{children}</AuthProvider>
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export default App
|
|
48
|
-
|
|
49
45
|
```
|
|
50
46
|
|
|
51
47
|
## Hook: useAuth
|
|
@@ -53,25 +49,25 @@ export default App
|
|
|
53
49
|
```bash
|
|
54
50
|
import { useAuth } from 'nextauthz'
|
|
55
51
|
|
|
56
|
-
const { user, login, logout, setUser,
|
|
52
|
+
const { user, role, login, logout, setUser, isAuthenticated } = useAuth()
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
Available Properties
|
|
60
56
|
|
|
61
|
-
| Name
|
|
62
|
-
|
|
|
63
|
-
| `user`
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
57
|
+
| Name | Type | Description | |
|
|
58
|
+
| ----------------- | ---------------------------------- | -------------------------------------- | -------------------------- |
|
|
59
|
+
| `user` | `User | null` | Current authenticated user |
|
|
60
|
+
| `role` | `string | null` | User role |
|
|
61
|
+
| `isAuthenticated` | `boolean` | True if the user is logged in | |
|
|
62
|
+
| `login` | `(tokens, userData, role) => void` | Logs in a user and saves tokens + role | |
|
|
63
|
+
| `logout` | `() => void` | Clears auth tokens and resets state | |
|
|
64
|
+
| `setUser` | `(user: User | null) => void` | Update user state manually |
|
|
69
65
|
|
|
70
66
|
|
|
71
67
|
Example: Logging in
|
|
72
68
|
|
|
73
69
|
```bash
|
|
74
|
-
const handleLogin =
|
|
70
|
+
const handleLogin = () => {
|
|
75
71
|
const tokens = {
|
|
76
72
|
access_token: 'abc123',
|
|
77
73
|
refresh_token: 'xyz789',
|
|
@@ -83,9 +79,8 @@ const handleLogin = async () => {
|
|
|
83
79
|
role: 'admin',
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
login(tokens, user)
|
|
82
|
+
login(tokens, user, user.role)
|
|
87
83
|
}
|
|
88
|
-
|
|
89
84
|
```
|
|
90
85
|
|
|
91
86
|
## AuthGuard
|
|
@@ -96,13 +91,11 @@ Protect routes/pages to ensure only authenticated users can access them.
|
|
|
96
91
|
|
|
97
92
|
Props
|
|
98
93
|
|
|
99
|
-
| Name
|
|
100
|
-
|
|
|
101
|
-
| `children`
|
|
102
|
-
| `redirectTo`
|
|
103
|
-
| `
|
|
104
|
-
| `refreshToken` | `() => Promise<string>` | optional | Function to refresh expired token |
|
|
105
|
-
|
|
94
|
+
| Name | Type | Default | Description |
|
|
95
|
+
| ------------ | ----------- | -------- | -------------------------------------- |
|
|
96
|
+
| `children` | `ReactNode` | required | Components to render if authenticated |
|
|
97
|
+
| `redirectTo` | `string` | `/login` | Page to redirect unauthenticated users |
|
|
98
|
+
| `fallback` | `ReactNode` | `null` | Optional loading or fallback UI |
|
|
106
99
|
|
|
107
100
|
## Usage Example
|
|
108
101
|
|
|
@@ -111,7 +104,7 @@ import AuthGuard from 'nextauthz'
|
|
|
111
104
|
|
|
112
105
|
function DashboardPage() {
|
|
113
106
|
return (
|
|
114
|
-
<AuthGuard>
|
|
107
|
+
<AuthGuard fallback={<p>Loading...</p>}>
|
|
115
108
|
<h1>Dashboard</h1>
|
|
116
109
|
</AuthGuard>
|
|
117
110
|
)
|
|
@@ -121,37 +114,45 @@ Behavior:
|
|
|
121
114
|
|
|
122
115
|
** Redirects to /login if the user is not authenticated.
|
|
123
116
|
|
|
124
|
-
** Supports token refresh with refreshToken callback.
|
|
125
|
-
|
|
126
117
|
|
|
127
118
|
## RoleGuard
|
|
128
119
|
|
|
129
120
|
Purpose
|
|
130
121
|
|
|
131
|
-
|
|
122
|
+
RoleGuard restricts access to certain pages or components based on a user’s role. It assumes authentication is already handled (e.g., via AuthGuard or useAuth) and only checks whether the user’s role is allowed to access the content.
|
|
132
123
|
|
|
133
|
-
| Name | Type | Default | Description
|
|
134
|
-
| -------------- | ----------- | --------------- |
|
|
135
|
-
| `children` | `ReactNode` | required | Components to render if user role is allowed
|
|
136
|
-
| `allowedRoles` | `string[]` | required |
|
|
137
|
-
| `redirectTo` | `string` | `/unauthorized` | Redirect page for unauthorized roles
|
|
124
|
+
| Name | Type | Default | Description |
|
|
125
|
+
| -------------- | ----------- | --------------- | ------------------------------------------------------------- |
|
|
126
|
+
| `children` | `ReactNode` | required | Components to render if the user role is allowed |
|
|
127
|
+
| `allowedRoles` | `string[]` | required | List of roles allowed to access this page |
|
|
128
|
+
| `redirectTo` | `string` | `/unauthorized` | Redirect page for unauthorized roles |
|
|
129
|
+
| `fallback` | `ReactNode` | `null` | Optional loading or fallback UI displayed while checking role |
|
|
138
130
|
|
|
139
131
|
|
|
140
132
|
Usage Example
|
|
141
133
|
|
|
142
134
|
```bash
|
|
143
|
-
import RoleGuard from '
|
|
144
|
-
import { useAuth } from '
|
|
135
|
+
import RoleGuard from 'nextauthz'
|
|
136
|
+
import { useAuth } from 'nextauthz'
|
|
145
137
|
|
|
146
138
|
function AdminPage() {
|
|
139
|
+
const { user } = useAuth()
|
|
140
|
+
|
|
147
141
|
return (
|
|
148
|
-
<RoleGuard allowedRoles={['admin']}>
|
|
142
|
+
<RoleGuard allowedRoles={['admin']} fallback={<p>Checking permissions...</p>}>
|
|
149
143
|
<h1>Admin Dashboard</h1>
|
|
144
|
+
<p>Welcome, {user?.name}</p>
|
|
150
145
|
</RoleGuard>
|
|
151
146
|
)
|
|
152
147
|
}
|
|
153
148
|
```
|
|
154
149
|
|
|
150
|
+
Behavior:
|
|
151
|
+
|
|
152
|
+
** RoleGuard will show the fallback UI while the authentication state is loading (isAuthChecked is false).
|
|
153
|
+
**
|
|
154
|
+
|
|
155
|
+
|
|
155
156
|
## Storage Options
|
|
156
157
|
|
|
157
158
|
You can configure token storage:
|
package/src/AuthGuard.tsx
CHANGED
|
@@ -1,42 +1,35 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { useEffect
|
|
3
|
+
import React, { useEffect } from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
|
-
import { useTokenManager } from 'react-token-manager'
|
|
6
5
|
import { useAuthStore } from '../store/useGuardStore'
|
|
7
6
|
|
|
8
7
|
type AuthGuardProps = {
|
|
9
8
|
children: React.ReactNode
|
|
10
9
|
redirectTo?: string
|
|
11
|
-
|
|
10
|
+
fallback?: React.ReactNode
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
const AuthGuard = ({
|
|
15
14
|
children,
|
|
16
15
|
redirectTo = '/login',
|
|
17
|
-
|
|
16
|
+
fallback = null,
|
|
18
17
|
}: AuthGuardProps) => {
|
|
19
18
|
const router = useRouter()
|
|
20
|
-
const manager = useTokenManager()
|
|
21
|
-
const setAuth = useAuthStore((state) => state.setAuth)
|
|
22
|
-
const setAuthChecked = useAuthStore((state) => state.setAuthChecked)
|
|
23
|
-
const [loading, setLoading] = useState(true)
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
const token = manager.getSingleToken(tokenKey)
|
|
27
|
-
const isValid = Boolean(token) && !manager.isExpired(token as any)
|
|
20
|
+
const { isAuthenticated, isAuthChecked } = useAuthStore()
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!isAuthChecked) return
|
|
31
24
|
|
|
32
|
-
if (!
|
|
25
|
+
if (!isAuthenticated) {
|
|
33
26
|
router.replace(redirectTo)
|
|
34
|
-
} else {
|
|
35
|
-
setLoading(false)
|
|
36
27
|
}
|
|
37
|
-
}, [
|
|
28
|
+
}, [isAuthenticated, isAuthChecked, redirectTo, router])
|
|
29
|
+
|
|
30
|
+
if (!isAuthChecked) return <>{fallback}</>
|
|
38
31
|
|
|
39
|
-
if (
|
|
32
|
+
if (!isAuthenticated) return null
|
|
40
33
|
|
|
41
34
|
return <>{children}</>
|
|
42
35
|
}
|
package/src/AuthProvider.tsx
CHANGED
|
@@ -1,93 +1,117 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
createContext,
|
|
5
|
-
useContext,
|
|
6
|
-
ReactNode,
|
|
7
|
-
useEffect,
|
|
8
|
-
useRef,
|
|
9
|
-
} from 'react'
|
|
3
|
+
import React, { createContext, useContext, ReactNode, useEffect } from 'react'
|
|
10
4
|
import { configureTokenManager, useTokenManager } from 'react-token-manager'
|
|
11
5
|
import { useAuthStore, User } from '../store/useGuardStore'
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
/* ---------------------------------- */
|
|
8
|
+
/* Types */
|
|
9
|
+
/* ---------------------------------- */
|
|
10
|
+
|
|
11
|
+
export type AuthContextType<UserType extends User = User> = {
|
|
14
12
|
user: UserType | null
|
|
15
|
-
|
|
13
|
+
role: string | null
|
|
14
|
+
isAuthenticated: boolean
|
|
15
|
+
login: (tokens: Record<string, string>, userData: UserType, role: string) => void
|
|
16
16
|
logout: () => void
|
|
17
|
-
setUser: (user: UserType) => void
|
|
18
|
-
|
|
19
|
-
error: Error | null
|
|
17
|
+
setUser: (user: UserType | null) => void
|
|
18
|
+
tokenKey: string
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
/* ---------------------------------- */
|
|
22
|
+
/* Factory */
|
|
23
|
+
/* ---------------------------------- */
|
|
24
|
+
|
|
25
|
+
export function createAuthContext<UserType extends User = User>(option?: {
|
|
23
26
|
storage?: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
24
|
-
|
|
27
|
+
tokenKey?: string
|
|
28
|
+
}) {
|
|
29
|
+
const AuthContext = createContext<AuthContextType<UserType> | undefined>(undefined)
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const { storage = 'cookie' } = options
|
|
31
|
+
const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
32
|
+
const storage = option?.storage ?? 'cookie'
|
|
33
|
+
const tokenKey = option?.tokenKey ?? 'access_token'
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
configureTokenManager({ storage })
|
|
37
|
+
}, [storage])
|
|
34
38
|
|
|
35
|
-
const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
36
|
-
const configuredRef = useRef(false)
|
|
37
39
|
const manager = useTokenManager()
|
|
38
40
|
|
|
39
|
-
const {
|
|
40
|
-
|
|
41
|
+
const {
|
|
42
|
+
user,
|
|
43
|
+
role,
|
|
44
|
+
setUser,
|
|
45
|
+
setRole,
|
|
46
|
+
resetAuth,
|
|
47
|
+
isAuthenticated,
|
|
48
|
+
setAuth,
|
|
49
|
+
setAuthChecked,
|
|
50
|
+
} = useAuthStore()
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
configureTokenManager({ storage })
|
|
46
|
-
configuredRef.current = true
|
|
47
|
-
}
|
|
48
|
-
}, [storage])
|
|
52
|
+
/* ---------------------------------- */
|
|
53
|
+
/* Hydrate user from storage */
|
|
54
|
+
/* ---------------------------------- */
|
|
49
55
|
|
|
50
|
-
// Restore user from token storage
|
|
51
56
|
useEffect(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const storedUser = manager.getSingleToken('user')
|
|
58
|
+
const token = manager.getSingleToken(tokenKey)
|
|
59
|
+
|
|
60
|
+
if (storedUser && token && !manager.isExpired(token)) {
|
|
61
|
+
try {
|
|
62
|
+
const parsedUser = JSON.parse(storedUser) as UserType
|
|
63
|
+
setUser(parsedUser)
|
|
64
|
+
setRole((parsedUser as any)?.role ?? null)
|
|
65
|
+
setAuth(true)
|
|
66
|
+
} catch {
|
|
67
|
+
resetAuth()
|
|
56
68
|
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
} finally {
|
|
60
|
-
setLoading(false)
|
|
61
|
-
}
|
|
62
|
-
}, [manager, setUser, setLoading, setError])
|
|
63
|
-
|
|
64
|
-
const login = (tokens: Record<string, string>, userData: UserType) => {
|
|
65
|
-
try {
|
|
66
|
-
manager.setTokens({ ...tokens, user: JSON.stringify(userData) })
|
|
67
|
-
setUser(userData)
|
|
68
|
-
} catch (err) {
|
|
69
|
-
setError(err instanceof Error ? err : new Error(String(err)))
|
|
69
|
+
} else {
|
|
70
|
+
resetAuth()
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
setAuthChecked(true)
|
|
74
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
|
+
}, [tokenKey])
|
|
76
|
+
|
|
77
|
+
/* ---------------------------------- */
|
|
78
|
+
/* Login */
|
|
79
|
+
/* ---------------------------------- */
|
|
80
|
+
|
|
81
|
+
const login = (tokens: Record<string, string>, userData: UserType, role: string) => {
|
|
82
|
+
// Use dynamic token key
|
|
83
|
+
const tokenValue = tokens[tokenKey] ?? tokens['access_token'] ?? Object.values(tokens)[0]
|
|
84
|
+
|
|
85
|
+
manager.setTokens({
|
|
86
|
+
...tokens,
|
|
87
|
+
[tokenKey]: tokenValue,
|
|
88
|
+
user: JSON.stringify(userData),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
setUser(userData)
|
|
92
|
+
setRole(role)
|
|
93
|
+
setAuth(true)
|
|
71
94
|
}
|
|
72
95
|
|
|
96
|
+
/* ---------------------------------- */
|
|
97
|
+
/* Logout */
|
|
98
|
+
/* ---------------------------------- */
|
|
99
|
+
|
|
73
100
|
const logout = () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
resetAuth()
|
|
77
|
-
} catch (err) {
|
|
78
|
-
setError(err instanceof Error ? err : new Error(String(err)))
|
|
79
|
-
}
|
|
101
|
+
manager.clearTokens()
|
|
102
|
+
resetAuth()
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
return (
|
|
83
106
|
<AuthContext.Provider
|
|
84
107
|
value={{
|
|
85
108
|
user: user as UserType | null,
|
|
109
|
+
role,
|
|
110
|
+
isAuthenticated,
|
|
86
111
|
login,
|
|
87
112
|
logout,
|
|
88
|
-
setUser: (u
|
|
89
|
-
|
|
90
|
-
error,
|
|
113
|
+
setUser: (u) => setUser(u),
|
|
114
|
+
tokenKey,
|
|
91
115
|
}}
|
|
92
116
|
>
|
|
93
117
|
{children}
|
|
@@ -95,11 +119,19 @@ export function createAuthContext<UserType extends User = User>(
|
|
|
95
119
|
)
|
|
96
120
|
}
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
122
|
+
/* ---------------------------------- */
|
|
123
|
+
/* Hook */
|
|
124
|
+
/* ---------------------------------- */
|
|
125
|
+
|
|
126
|
+
const useAuth = () => {
|
|
127
|
+
const context = useContext(AuthContext)
|
|
128
|
+
if (!context) throw new Error('useAuth must be used within AuthProvider')
|
|
129
|
+
return context
|
|
102
130
|
}
|
|
103
131
|
|
|
104
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
AuthProvider,
|
|
134
|
+
useAuth,
|
|
135
|
+
tokenKey: option?.tokenKey ?? 'access_token',
|
|
136
|
+
}
|
|
105
137
|
}
|
package/src/RoleGuard.tsx
CHANGED
|
@@ -8,44 +8,32 @@ type RoleGuardProps = {
|
|
|
8
8
|
children: React.ReactNode
|
|
9
9
|
allowedRoles: string[]
|
|
10
10
|
redirectTo?: string
|
|
11
|
+
fallback?: React.ReactNode
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
const RoleGuard = ({
|
|
14
15
|
children,
|
|
15
16
|
allowedRoles,
|
|
16
17
|
redirectTo = '/unauthorized',
|
|
18
|
+
fallback = null,
|
|
17
19
|
}: RoleGuardProps) => {
|
|
18
20
|
const router = useRouter()
|
|
19
|
-
|
|
20
|
-
// Get auth state from Zustand
|
|
21
|
-
const { user, isAuthChecked, isAuthenticated } = useAuthStore()
|
|
21
|
+
const { role, isAuthChecked } = useAuthStore()
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
|
-
// Wait until auth is checked
|
|
25
24
|
if (!isAuthChecked) return
|
|
26
25
|
|
|
27
|
-
//
|
|
28
|
-
if (!
|
|
29
|
-
router.replace('/login')
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Role check
|
|
34
|
-
const role = user?.role
|
|
35
|
-
if (!allowedRoles.includes(role)) {
|
|
26
|
+
// If role not allowed, redirect
|
|
27
|
+
if (!role || !allowedRoles.includes(role)) {
|
|
36
28
|
router.replace(redirectTo)
|
|
37
29
|
}
|
|
38
|
-
}, [
|
|
39
|
-
|
|
40
|
-
// Show loading until auth is checked
|
|
41
|
-
if (!isAuthChecked) return <div>Loading...</div>
|
|
30
|
+
}, [role, isAuthChecked, allowedRoles, redirectTo, router])
|
|
42
31
|
|
|
43
|
-
//
|
|
44
|
-
if (!
|
|
32
|
+
// Show fallback while loading
|
|
33
|
+
if (!isAuthChecked) return <>{fallback}</>
|
|
45
34
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
if (!allowedRoles.includes(role)) return null
|
|
35
|
+
// Block rendering if role is not allowed
|
|
36
|
+
if (!role || !allowedRoles.includes(role)) return null
|
|
49
37
|
|
|
50
38
|
return <>{children}</>
|
|
51
39
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,14 @@ import { createAuthContext } from './AuthProvider'
|
|
|
5
5
|
export { default as AuthGuard } from './AuthGuard'
|
|
6
6
|
export { default as RoleGuard } from './RoleGuard'
|
|
7
7
|
|
|
8
|
-
export type User = any
|
|
8
|
+
export type User = Record<string, any>
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Factory to create isolated auth instance
|
|
12
|
+
*/
|
|
10
13
|
export function createAppAuth(
|
|
11
|
-
storage: 'localStorage' | 'sessionStorage' | 'cookie' = 'cookie'
|
|
14
|
+
storage: 'localStorage' | 'sessionStorage' | 'cookie' = 'cookie',
|
|
15
|
+
tokenKey: string = 'access_token' // <-- default token key
|
|
12
16
|
) {
|
|
13
|
-
return createAuthContext({ storage })
|
|
17
|
+
return createAuthContext({ storage, tokenKey })
|
|
14
18
|
}
|
package/store/useGuardStore.ts
CHANGED
|
@@ -7,12 +7,16 @@ export type User = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
type AuthState = {
|
|
10
|
-
user: User | null
|
|
10
|
+
user: User | null
|
|
11
|
+
role: string | null // ✅ explicit role
|
|
12
|
+
|
|
11
13
|
isAuthenticated: boolean
|
|
12
14
|
isAuthChecked: boolean
|
|
13
|
-
loading: boolean
|
|
15
|
+
loading: boolean
|
|
14
16
|
error: Error | null
|
|
17
|
+
|
|
15
18
|
setUser: (user: User | null) => void
|
|
19
|
+
setRole: (role: string | null) => void // ✅ new setter
|
|
16
20
|
setAuth: (isAuth: boolean) => void
|
|
17
21
|
setAuthChecked: (checked: boolean) => void
|
|
18
22
|
setLoading: (loading: boolean) => void
|
|
@@ -22,12 +26,16 @@ type AuthState = {
|
|
|
22
26
|
|
|
23
27
|
export const useAuthStore = create<AuthState>((set) => ({
|
|
24
28
|
user: null,
|
|
29
|
+
role: null,
|
|
30
|
+
|
|
25
31
|
isAuthenticated: false,
|
|
26
32
|
isAuthChecked: false,
|
|
27
|
-
loading: true,
|
|
33
|
+
loading: true,
|
|
28
34
|
error: null,
|
|
29
35
|
|
|
30
36
|
setUser: (user) => set({ user }),
|
|
37
|
+
setRole: (role) => set({ role }),
|
|
38
|
+
|
|
31
39
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
32
40
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
33
41
|
setLoading: (loading) => set({ loading }),
|
|
@@ -36,6 +44,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|
|
36
44
|
resetAuth: () =>
|
|
37
45
|
set({
|
|
38
46
|
user: null,
|
|
47
|
+
role: null,
|
|
39
48
|
isAuthenticated: false,
|
|
40
49
|
isAuthChecked: false,
|
|
41
50
|
loading: false,
|