nextauthz 1.1.9 → 1.2.1
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 +10 -11
- package/dist/index.d.ts +10 -11
- package/dist/index.js +72 -94
- package/dist/index.mjs +75 -95
- package/package.json +1 -1
- package/src/AuthGuard.tsx +14 -37
- package/src/AuthProvider.tsx +45 -51
- package/src/RoleGuard.tsx +26 -10
- package/src/index.ts +2 -6
- package/store/useGuardStore.ts +6 -1
package/dist/index.d.mts
CHANGED
|
@@ -2,9 +2,13 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type User$1 = {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type AuthContextType<UserType extends User$1> = {
|
|
6
10
|
user: UserType | null;
|
|
7
|
-
login: (tokens: Record<string, string>,
|
|
11
|
+
login: (tokens: Record<string, string>, userData: UserType) => void;
|
|
8
12
|
logout: () => void;
|
|
9
13
|
setUser: (user: UserType) => void;
|
|
10
14
|
loading: boolean;
|
|
@@ -15,27 +19,22 @@ type AuthGuardProps = {
|
|
|
15
19
|
children: React__default.ReactNode;
|
|
16
20
|
redirectTo?: string;
|
|
17
21
|
tokenKey?: string;
|
|
18
|
-
refreshToken?: () => Promise<string | null>;
|
|
19
22
|
};
|
|
20
|
-
declare const AuthGuard: ({ children, redirectTo, tokenKey,
|
|
23
|
+
declare const AuthGuard: ({ children, redirectTo, tokenKey, }: AuthGuardProps) => react_jsx_runtime.JSX.Element;
|
|
21
24
|
|
|
22
25
|
type RoleGuardProps = {
|
|
23
26
|
children: React__default.ReactNode;
|
|
24
27
|
allowedRoles: string[];
|
|
25
28
|
redirectTo?: string;
|
|
26
29
|
};
|
|
27
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element;
|
|
30
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
28
31
|
|
|
29
32
|
type User = any;
|
|
30
33
|
declare function createAppAuth(storage?: 'localStorage' | 'sessionStorage' | 'cookie'): {
|
|
31
34
|
AuthProvider: ({ children }: {
|
|
32
35
|
children: React.ReactNode;
|
|
33
36
|
}) => react_jsx_runtime.JSX.Element;
|
|
34
|
-
useAuth: () => AuthContextType<
|
|
37
|
+
useAuth: () => AuthContextType<User$1>;
|
|
35
38
|
};
|
|
36
|
-
declare const AuthProvider: ({ children }: {
|
|
37
|
-
children: React.ReactNode;
|
|
38
|
-
}) => react_jsx_runtime.JSX.Element;
|
|
39
|
-
declare const useAuth: () => AuthContextType<any>;
|
|
40
39
|
|
|
41
|
-
export { AuthGuard,
|
|
40
|
+
export { AuthGuard, RoleGuard, type User, createAppAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,13 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type User$1 = {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type AuthContextType<UserType extends User$1> = {
|
|
6
10
|
user: UserType | null;
|
|
7
|
-
login: (tokens: Record<string, string>,
|
|
11
|
+
login: (tokens: Record<string, string>, userData: UserType) => void;
|
|
8
12
|
logout: () => void;
|
|
9
13
|
setUser: (user: UserType) => void;
|
|
10
14
|
loading: boolean;
|
|
@@ -15,27 +19,22 @@ type AuthGuardProps = {
|
|
|
15
19
|
children: React__default.ReactNode;
|
|
16
20
|
redirectTo?: string;
|
|
17
21
|
tokenKey?: string;
|
|
18
|
-
refreshToken?: () => Promise<string | null>;
|
|
19
22
|
};
|
|
20
|
-
declare const AuthGuard: ({ children, redirectTo, tokenKey,
|
|
23
|
+
declare const AuthGuard: ({ children, redirectTo, tokenKey, }: AuthGuardProps) => react_jsx_runtime.JSX.Element;
|
|
21
24
|
|
|
22
25
|
type RoleGuardProps = {
|
|
23
26
|
children: React__default.ReactNode;
|
|
24
27
|
allowedRoles: string[];
|
|
25
28
|
redirectTo?: string;
|
|
26
29
|
};
|
|
27
|
-
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element;
|
|
30
|
+
declare const RoleGuard: ({ children, allowedRoles, redirectTo, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
|
|
28
31
|
|
|
29
32
|
type User = any;
|
|
30
33
|
declare function createAppAuth(storage?: 'localStorage' | 'sessionStorage' | 'cookie'): {
|
|
31
34
|
AuthProvider: ({ children }: {
|
|
32
35
|
children: React.ReactNode;
|
|
33
36
|
}) => react_jsx_runtime.JSX.Element;
|
|
34
|
-
useAuth: () => AuthContextType<
|
|
37
|
+
useAuth: () => AuthContextType<User$1>;
|
|
35
38
|
};
|
|
36
|
-
declare const AuthProvider: ({ children }: {
|
|
37
|
-
children: React.ReactNode;
|
|
38
|
-
}) => react_jsx_runtime.JSX.Element;
|
|
39
|
-
declare const useAuth: () => AuthContextType<any>;
|
|
40
39
|
|
|
41
|
-
export { AuthGuard,
|
|
40
|
+
export { AuthGuard, RoleGuard, type User, createAppAuth };
|
package/dist/index.js
CHANGED
|
@@ -22,10 +22,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
AuthGuard: () => AuthGuard_default,
|
|
25
|
-
AuthProvider: () => AuthProvider,
|
|
26
25
|
RoleGuard: () => RoleGuard_default,
|
|
27
|
-
createAppAuth: () => createAppAuth
|
|
28
|
-
useAuth: () => useAuth
|
|
26
|
+
createAppAuth: () => createAppAuth
|
|
29
27
|
});
|
|
30
28
|
module.exports = __toCommonJS(index_exports);
|
|
31
29
|
|
|
@@ -39,88 +37,89 @@ var useAuthStore = (0, import_zustand.create)((set) => ({
|
|
|
39
37
|
user: null,
|
|
40
38
|
isAuthenticated: false,
|
|
41
39
|
isAuthChecked: false,
|
|
40
|
+
loading: true,
|
|
41
|
+
// start as loading
|
|
42
42
|
error: null,
|
|
43
43
|
setUser: (user) => set({ user }),
|
|
44
44
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
45
45
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
46
|
+
setLoading: (loading) => set({ loading }),
|
|
46
47
|
setError: (err) => set({ error: err }),
|
|
47
48
|
resetAuth: () => set({
|
|
48
49
|
user: null,
|
|
49
50
|
isAuthenticated: false,
|
|
50
51
|
isAuthChecked: false,
|
|
52
|
+
loading: false,
|
|
51
53
|
error: null
|
|
52
54
|
})
|
|
53
55
|
}));
|
|
54
56
|
|
|
55
57
|
// src/AuthProvider.tsx
|
|
56
58
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
57
|
-
function createAuthContext(options) {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
function createAuthContext(options = {}) {
|
|
60
|
+
const { storage = "cookie" } = options;
|
|
61
|
+
const AuthContext = (0, import_react.createContext)(
|
|
62
|
+
void 0
|
|
63
|
+
);
|
|
64
|
+
const AuthProvider = ({ children }) => {
|
|
65
|
+
const configuredRef = (0, import_react.useRef)(false);
|
|
64
66
|
const manager = (0, import_react_token_manager.useTokenManager)();
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const { user, loading, error, setUser, setLoading, setError, resetAuth } = useAuthStore();
|
|
68
|
+
(0, import_react.useEffect)(() => {
|
|
69
|
+
if (!configuredRef.current) {
|
|
70
|
+
(0, import_react_token_manager.configureTokenManager)({ storage });
|
|
71
|
+
configuredRef.current = true;
|
|
72
|
+
}
|
|
73
|
+
}, [storage]);
|
|
69
74
|
(0, import_react.useEffect)(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
useAuthStore.getState().setUser(parsedUser);
|
|
75
|
-
useAuthStore.getState().setError(null);
|
|
76
|
-
} catch {
|
|
77
|
-
useAuthStore.getState().resetAuth();
|
|
78
|
-
useAuthStore.getState().setError(
|
|
79
|
-
new Error("Failed to parse saved user")
|
|
80
|
-
);
|
|
75
|
+
try {
|
|
76
|
+
const savedUser = manager.getSingleToken("user");
|
|
77
|
+
if (savedUser) {
|
|
78
|
+
setUser(JSON.parse(savedUser));
|
|
81
79
|
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
82
|
+
} finally {
|
|
83
|
+
setLoading(false);
|
|
82
84
|
}
|
|
83
|
-
|
|
84
|
-
}, [manager]);
|
|
85
|
-
const setUser = (userData) => {
|
|
86
|
-
useAuthStore.getState().setUser(userData);
|
|
87
|
-
useAuthStore.getState().setError(null);
|
|
88
|
-
manager.setTokens({ user: JSON.stringify(userData) });
|
|
89
|
-
};
|
|
85
|
+
}, [manager, setUser, setLoading, setError]);
|
|
90
86
|
const login = (tokens, userData) => {
|
|
91
87
|
try {
|
|
92
|
-
manager.setTokens(tokens);
|
|
88
|
+
manager.setTokens({ ...tokens, user: JSON.stringify(userData) });
|
|
93
89
|
setUser(userData);
|
|
94
90
|
} catch (err) {
|
|
95
|
-
|
|
96
|
-
err instanceof Error ? err : new Error(String(err))
|
|
97
|
-
);
|
|
91
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
98
92
|
}
|
|
99
93
|
};
|
|
100
94
|
const logout = () => {
|
|
101
95
|
try {
|
|
102
96
|
manager.clearTokens();
|
|
103
|
-
|
|
97
|
+
resetAuth();
|
|
104
98
|
} catch (err) {
|
|
105
|
-
|
|
106
|
-
err instanceof Error ? err : new Error(String(err))
|
|
107
|
-
);
|
|
99
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
108
100
|
}
|
|
109
101
|
};
|
|
110
102
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
111
103
|
AuthContext.Provider,
|
|
112
104
|
{
|
|
113
|
-
value: {
|
|
105
|
+
value: {
|
|
106
|
+
user,
|
|
107
|
+
login,
|
|
108
|
+
logout,
|
|
109
|
+
setUser: (u) => setUser(u),
|
|
110
|
+
loading,
|
|
111
|
+
error
|
|
112
|
+
},
|
|
114
113
|
children
|
|
115
114
|
}
|
|
116
115
|
);
|
|
117
116
|
};
|
|
118
|
-
const
|
|
117
|
+
const useAuth = () => {
|
|
119
118
|
const ctx = (0, import_react.useContext)(AuthContext);
|
|
120
119
|
if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
|
|
121
120
|
return ctx;
|
|
122
121
|
};
|
|
123
|
-
return { AuthProvider
|
|
122
|
+
return { AuthProvider, useAuth };
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
// src/AuthGuard.tsx
|
|
@@ -131,48 +130,25 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
131
130
|
var AuthGuard = ({
|
|
132
131
|
children,
|
|
133
132
|
redirectTo = "/login",
|
|
134
|
-
tokenKey = "access_token"
|
|
135
|
-
refreshToken
|
|
133
|
+
tokenKey = "access_token"
|
|
136
134
|
}) => {
|
|
137
|
-
const manager = (0, import_react_token_manager2.useTokenManager)();
|
|
138
135
|
const router = (0, import_navigation.useRouter)();
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
136
|
+
const manager = (0, import_react_token_manager2.useTokenManager)();
|
|
137
|
+
const setAuth = useAuthStore((state) => state.setAuth);
|
|
138
|
+
const setAuthChecked = useAuthStore((state) => state.setAuthChecked);
|
|
139
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
142
140
|
(0, import_react2.useEffect)(() => {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
const isValid = token && !manager.isExpired(token);
|
|
156
|
-
useAuthStore.getState().setAuth(Boolean(isValid));
|
|
157
|
-
if (!isValid) {
|
|
158
|
-
router.replace(redirectTo);
|
|
159
|
-
}
|
|
160
|
-
} catch (err) {
|
|
161
|
-
useAuthStore.getState().setError(err instanceof Error ? err : new Error(String(err)));
|
|
162
|
-
useAuthStore.getState().setAuth(false);
|
|
163
|
-
router.replace(redirectTo);
|
|
164
|
-
} finally {
|
|
165
|
-
useAuthStore.getState().setAuthChecked(true);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
checkAuth();
|
|
169
|
-
}, [manager, router, redirectTo, tokenKey, refreshToken]);
|
|
170
|
-
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "Loading..." });
|
|
171
|
-
if (error) return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
172
|
-
"Error: ",
|
|
173
|
-
error.message
|
|
174
|
-
] });
|
|
175
|
-
if (!isAuthenticated) return null;
|
|
141
|
+
const token = manager.getSingleToken(tokenKey);
|
|
142
|
+
const isValid = Boolean(token) && !manager.isExpired(token);
|
|
143
|
+
setAuth(Boolean(isValid));
|
|
144
|
+
setAuthChecked(true);
|
|
145
|
+
if (!isValid) {
|
|
146
|
+
router.replace(redirectTo);
|
|
147
|
+
} else {
|
|
148
|
+
setLoading(false);
|
|
149
|
+
}
|
|
150
|
+
}, [manager, tokenKey, redirectTo, router, setAuth, setAuthChecked]);
|
|
151
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: "Loading..." });
|
|
176
152
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
177
153
|
};
|
|
178
154
|
var AuthGuard_default = AuthGuard;
|
|
@@ -186,18 +162,23 @@ var RoleGuard = ({
|
|
|
186
162
|
allowedRoles,
|
|
187
163
|
redirectTo = "/unauthorized"
|
|
188
164
|
}) => {
|
|
189
|
-
const { user, loading } = useAuth();
|
|
190
165
|
const router = (0, import_navigation2.useRouter)();
|
|
191
|
-
const
|
|
166
|
+
const { user, isAuthChecked, isAuthenticated } = useAuthStore();
|
|
192
167
|
(0, import_react3.useEffect)(() => {
|
|
193
|
-
if (!
|
|
194
|
-
|
|
195
|
-
|
|
168
|
+
if (!isAuthChecked) return;
|
|
169
|
+
if (!isAuthenticated || !user) {
|
|
170
|
+
router.replace("/login");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const role2 = user?.role;
|
|
174
|
+
if (!allowedRoles.includes(role2)) {
|
|
196
175
|
router.replace(redirectTo);
|
|
197
176
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (
|
|
177
|
+
}, [isAuthChecked, isAuthenticated, user, allowedRoles, redirectTo, router]);
|
|
178
|
+
if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: "Loading..." });
|
|
179
|
+
if (!isAuthenticated || !user) return null;
|
|
180
|
+
const role = user?.role;
|
|
181
|
+
if (!allowedRoles.includes(role)) return null;
|
|
201
182
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
|
|
202
183
|
};
|
|
203
184
|
var RoleGuard_default = RoleGuard;
|
|
@@ -206,12 +187,9 @@ var RoleGuard_default = RoleGuard;
|
|
|
206
187
|
function createAppAuth(storage = "cookie") {
|
|
207
188
|
return createAuthContext({ storage });
|
|
208
189
|
}
|
|
209
|
-
var { AuthProvider, useAuth } = createAppAuth();
|
|
210
190
|
// Annotate the CommonJS export names for ESM import in node:
|
|
211
191
|
0 && (module.exports = {
|
|
212
192
|
AuthGuard,
|
|
213
|
-
AuthProvider,
|
|
214
193
|
RoleGuard,
|
|
215
|
-
createAppAuth
|
|
216
|
-
useAuth
|
|
194
|
+
createAppAuth
|
|
217
195
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createContext,
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
|
-
|
|
8
|
+
useRef
|
|
9
9
|
} from "react";
|
|
10
10
|
import { configureTokenManager, useTokenManager } from "react-token-manager";
|
|
11
11
|
|
|
@@ -15,146 +15,124 @@ var useAuthStore = create((set) => ({
|
|
|
15
15
|
user: null,
|
|
16
16
|
isAuthenticated: false,
|
|
17
17
|
isAuthChecked: false,
|
|
18
|
+
loading: true,
|
|
19
|
+
// start as loading
|
|
18
20
|
error: null,
|
|
19
21
|
setUser: (user) => set({ user }),
|
|
20
22
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
21
23
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
24
|
+
setLoading: (loading) => set({ loading }),
|
|
22
25
|
setError: (err) => set({ error: err }),
|
|
23
26
|
resetAuth: () => set({
|
|
24
27
|
user: null,
|
|
25
28
|
isAuthenticated: false,
|
|
26
29
|
isAuthChecked: false,
|
|
30
|
+
loading: false,
|
|
27
31
|
error: null
|
|
28
32
|
})
|
|
29
33
|
}));
|
|
30
34
|
|
|
31
35
|
// src/AuthProvider.tsx
|
|
32
36
|
import { jsx } from "react/jsx-runtime";
|
|
33
|
-
function createAuthContext(options) {
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
function createAuthContext(options = {}) {
|
|
38
|
+
const { storage = "cookie" } = options;
|
|
39
|
+
const AuthContext = createContext(
|
|
40
|
+
void 0
|
|
41
|
+
);
|
|
42
|
+
const AuthProvider = ({ children }) => {
|
|
43
|
+
const configuredRef = useRef(false);
|
|
40
44
|
const manager = useTokenManager();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const { user, loading, error, setUser, setLoading, setError, resetAuth } = useAuthStore();
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!configuredRef.current) {
|
|
48
|
+
configureTokenManager({ storage });
|
|
49
|
+
configuredRef.current = true;
|
|
50
|
+
}
|
|
51
|
+
}, [storage]);
|
|
45
52
|
useEffect(() => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
useAuthStore.getState().setUser(parsedUser);
|
|
51
|
-
useAuthStore.getState().setError(null);
|
|
52
|
-
} catch {
|
|
53
|
-
useAuthStore.getState().resetAuth();
|
|
54
|
-
useAuthStore.getState().setError(
|
|
55
|
-
new Error("Failed to parse saved user")
|
|
56
|
-
);
|
|
53
|
+
try {
|
|
54
|
+
const savedUser = manager.getSingleToken("user");
|
|
55
|
+
if (savedUser) {
|
|
56
|
+
setUser(JSON.parse(savedUser));
|
|
57
57
|
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
60
|
+
} finally {
|
|
61
|
+
setLoading(false);
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
}, [manager]);
|
|
61
|
-
const setUser = (userData) => {
|
|
62
|
-
useAuthStore.getState().setUser(userData);
|
|
63
|
-
useAuthStore.getState().setError(null);
|
|
64
|
-
manager.setTokens({ user: JSON.stringify(userData) });
|
|
65
|
-
};
|
|
63
|
+
}, [manager, setUser, setLoading, setError]);
|
|
66
64
|
const login = (tokens, userData) => {
|
|
67
65
|
try {
|
|
68
|
-
manager.setTokens(tokens);
|
|
66
|
+
manager.setTokens({ ...tokens, user: JSON.stringify(userData) });
|
|
69
67
|
setUser(userData);
|
|
70
68
|
} catch (err) {
|
|
71
|
-
|
|
72
|
-
err instanceof Error ? err : new Error(String(err))
|
|
73
|
-
);
|
|
69
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
74
70
|
}
|
|
75
71
|
};
|
|
76
72
|
const logout = () => {
|
|
77
73
|
try {
|
|
78
74
|
manager.clearTokens();
|
|
79
|
-
|
|
75
|
+
resetAuth();
|
|
80
76
|
} catch (err) {
|
|
81
|
-
|
|
82
|
-
err instanceof Error ? err : new Error(String(err))
|
|
83
|
-
);
|
|
77
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
84
78
|
}
|
|
85
79
|
};
|
|
86
80
|
return /* @__PURE__ */ jsx(
|
|
87
81
|
AuthContext.Provider,
|
|
88
82
|
{
|
|
89
|
-
value: {
|
|
83
|
+
value: {
|
|
84
|
+
user,
|
|
85
|
+
login,
|
|
86
|
+
logout,
|
|
87
|
+
setUser: (u) => setUser(u),
|
|
88
|
+
loading,
|
|
89
|
+
error
|
|
90
|
+
},
|
|
90
91
|
children
|
|
91
92
|
}
|
|
92
93
|
);
|
|
93
94
|
};
|
|
94
|
-
const
|
|
95
|
+
const useAuth = () => {
|
|
95
96
|
const ctx = useContext(AuthContext);
|
|
96
97
|
if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
|
|
97
98
|
return ctx;
|
|
98
99
|
};
|
|
99
|
-
return { AuthProvider
|
|
100
|
+
return { AuthProvider, useAuth };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
// src/AuthGuard.tsx
|
|
103
|
-
import { useEffect as useEffect2 } from "react";
|
|
104
|
+
import { useEffect as useEffect2, useState } from "react";
|
|
104
105
|
import { useRouter } from "next/navigation";
|
|
105
106
|
import { useTokenManager as useTokenManager2 } from "react-token-manager";
|
|
106
|
-
import { Fragment, jsx as jsx2
|
|
107
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
107
108
|
var AuthGuard = ({
|
|
108
109
|
children,
|
|
109
110
|
redirectTo = "/login",
|
|
110
|
-
tokenKey = "access_token"
|
|
111
|
-
refreshToken
|
|
111
|
+
tokenKey = "access_token"
|
|
112
112
|
}) => {
|
|
113
|
-
const manager = useTokenManager2();
|
|
114
113
|
const router = useRouter();
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
114
|
+
const manager = useTokenManager2();
|
|
115
|
+
const setAuth = useAuthStore((state) => state.setAuth);
|
|
116
|
+
const setAuthChecked = useAuthStore((state) => state.setAuthChecked);
|
|
117
|
+
const [loading, setLoading] = useState(true);
|
|
118
118
|
useEffect2(() => {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
const isValid = token && !manager.isExpired(token);
|
|
132
|
-
useAuthStore.getState().setAuth(Boolean(isValid));
|
|
133
|
-
if (!isValid) {
|
|
134
|
-
router.replace(redirectTo);
|
|
135
|
-
}
|
|
136
|
-
} catch (err) {
|
|
137
|
-
useAuthStore.getState().setError(err instanceof Error ? err : new Error(String(err)));
|
|
138
|
-
useAuthStore.getState().setAuth(false);
|
|
139
|
-
router.replace(redirectTo);
|
|
140
|
-
} finally {
|
|
141
|
-
useAuthStore.getState().setAuthChecked(true);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
checkAuth();
|
|
145
|
-
}, [manager, router, redirectTo, tokenKey, refreshToken]);
|
|
146
|
-
if (!isAuthChecked) return /* @__PURE__ */ jsx2("div", { children: "Loading..." });
|
|
147
|
-
if (error) return /* @__PURE__ */ jsxs("div", { children: [
|
|
148
|
-
"Error: ",
|
|
149
|
-
error.message
|
|
150
|
-
] });
|
|
151
|
-
if (!isAuthenticated) return null;
|
|
119
|
+
const token = manager.getSingleToken(tokenKey);
|
|
120
|
+
const isValid = Boolean(token) && !manager.isExpired(token);
|
|
121
|
+
setAuth(Boolean(isValid));
|
|
122
|
+
setAuthChecked(true);
|
|
123
|
+
if (!isValid) {
|
|
124
|
+
router.replace(redirectTo);
|
|
125
|
+
} else {
|
|
126
|
+
setLoading(false);
|
|
127
|
+
}
|
|
128
|
+
}, [manager, tokenKey, redirectTo, router, setAuth, setAuthChecked]);
|
|
129
|
+
if (loading) return /* @__PURE__ */ jsx2("div", { children: "Loading..." });
|
|
152
130
|
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
153
131
|
};
|
|
154
132
|
var AuthGuard_default = AuthGuard;
|
|
155
133
|
|
|
156
134
|
// src/RoleGuard.tsx
|
|
157
|
-
import { useEffect as useEffect3
|
|
135
|
+
import { useEffect as useEffect3 } from "react";
|
|
158
136
|
import { useRouter as useRouter2 } from "next/navigation";
|
|
159
137
|
import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
|
|
160
138
|
var RoleGuard = ({
|
|
@@ -162,18 +140,23 @@ var RoleGuard = ({
|
|
|
162
140
|
allowedRoles,
|
|
163
141
|
redirectTo = "/unauthorized"
|
|
164
142
|
}) => {
|
|
165
|
-
const { user, loading } = useAuth();
|
|
166
143
|
const router = useRouter2();
|
|
167
|
-
const
|
|
144
|
+
const { user, isAuthChecked, isAuthenticated } = useAuthStore();
|
|
168
145
|
useEffect3(() => {
|
|
169
|
-
if (!
|
|
170
|
-
|
|
171
|
-
|
|
146
|
+
if (!isAuthChecked) return;
|
|
147
|
+
if (!isAuthenticated || !user) {
|
|
148
|
+
router.replace("/login");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const role2 = user?.role;
|
|
152
|
+
if (!allowedRoles.includes(role2)) {
|
|
172
153
|
router.replace(redirectTo);
|
|
173
154
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
155
|
+
}, [isAuthChecked, isAuthenticated, user, allowedRoles, redirectTo, router]);
|
|
156
|
+
if (!isAuthChecked) return /* @__PURE__ */ jsx3("div", { children: "Loading..." });
|
|
157
|
+
if (!isAuthenticated || !user) return null;
|
|
158
|
+
const role = user?.role;
|
|
159
|
+
if (!allowedRoles.includes(role)) return null;
|
|
177
160
|
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
178
161
|
};
|
|
179
162
|
var RoleGuard_default = RoleGuard;
|
|
@@ -182,11 +165,8 @@ var RoleGuard_default = RoleGuard;
|
|
|
182
165
|
function createAppAuth(storage = "cookie") {
|
|
183
166
|
return createAuthContext({ storage });
|
|
184
167
|
}
|
|
185
|
-
var { AuthProvider, useAuth } = createAppAuth();
|
|
186
168
|
export {
|
|
187
169
|
AuthGuard_default as AuthGuard,
|
|
188
|
-
AuthProvider,
|
|
189
170
|
RoleGuard_default as RoleGuard,
|
|
190
|
-
createAppAuth
|
|
191
|
-
useAuth
|
|
171
|
+
createAppAuth
|
|
192
172
|
};
|
package/package.json
CHANGED
package/src/AuthGuard.tsx
CHANGED
|
@@ -9,57 +9,34 @@ type AuthGuardProps = {
|
|
|
9
9
|
children: React.ReactNode
|
|
10
10
|
redirectTo?: string
|
|
11
11
|
tokenKey?: string
|
|
12
|
-
refreshToken?: () => Promise<string | null>
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
const AuthGuard = ({
|
|
16
15
|
children,
|
|
17
16
|
redirectTo = '/login',
|
|
18
17
|
tokenKey = 'access_token',
|
|
19
|
-
refreshToken,
|
|
20
18
|
}: AuthGuardProps) => {
|
|
21
|
-
const manager = useTokenManager()
|
|
22
19
|
const router = useRouter()
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
20
|
+
const manager = useTokenManager()
|
|
21
|
+
const setAuth = useAuthStore((state) => state.setAuth)
|
|
22
|
+
const setAuthChecked = useAuthStore((state) => state.setAuthChecked)
|
|
23
|
+
const [loading, setLoading] = useState(true)
|
|
26
24
|
|
|
27
25
|
useEffect(() => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
let token = manager.getSingleToken(tokenKey)
|
|
26
|
+
const token = manager.getSingleToken(tokenKey)
|
|
27
|
+
const isValid = Boolean(token) && !manager.isExpired(token as any)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const newToken = await refreshToken()
|
|
35
|
-
if (newToken) {
|
|
36
|
-
manager.setTokens({ [tokenKey]: newToken })
|
|
37
|
-
token = newToken
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
29
|
+
setAuth(Boolean(isValid))
|
|
30
|
+
setAuthChecked(true)
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
router.replace(redirectTo)
|
|
47
|
-
}
|
|
48
|
-
} catch (err: any) {
|
|
49
|
-
useAuthStore.getState().setError(err instanceof Error ? err : new Error(String(err)))
|
|
50
|
-
useAuthStore.getState().setAuth(false)
|
|
51
|
-
router.replace(redirectTo)
|
|
52
|
-
} finally {
|
|
53
|
-
useAuthStore.getState().setAuthChecked(true)
|
|
54
|
-
}
|
|
32
|
+
if (!isValid) {
|
|
33
|
+
router.replace(redirectTo)
|
|
34
|
+
} else {
|
|
35
|
+
setLoading(false)
|
|
55
36
|
}
|
|
37
|
+
}, [manager, tokenKey, redirectTo, router, setAuth, setAuthChecked])
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
}, [manager, router, redirectTo, tokenKey, refreshToken])
|
|
59
|
-
|
|
60
|
-
if (!isAuthChecked) return <div>Loading...</div>
|
|
61
|
-
if (error) return <div>Error: {error.message}</div>
|
|
62
|
-
if (!isAuthenticated) return null
|
|
39
|
+
if (loading) return <div>Loading...</div>
|
|
63
40
|
|
|
64
41
|
return <>{children}</>
|
|
65
42
|
}
|
package/src/AuthProvider.tsx
CHANGED
|
@@ -5,96 +5,90 @@ import React, {
|
|
|
5
5
|
useContext,
|
|
6
6
|
ReactNode,
|
|
7
7
|
useEffect,
|
|
8
|
-
|
|
8
|
+
useRef,
|
|
9
9
|
} from 'react'
|
|
10
10
|
import { configureTokenManager, useTokenManager } from 'react-token-manager'
|
|
11
|
-
import { useAuthStore } from '../store/useGuardStore'
|
|
11
|
+
import { useAuthStore, User } from '../store/useGuardStore'
|
|
12
12
|
|
|
13
|
-
export type AuthContextType<UserType> = {
|
|
13
|
+
export type AuthContextType<UserType extends User> = {
|
|
14
14
|
user: UserType | null
|
|
15
|
-
login: (tokens: Record<string, string>,
|
|
15
|
+
login: (tokens: Record<string, string>, userData: UserType) => void
|
|
16
16
|
logout: () => void
|
|
17
17
|
setUser: (user: UserType) => void
|
|
18
18
|
loading: boolean
|
|
19
19
|
error: Error | null
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
type CreateAuthOptions = {
|
|
23
23
|
storage?: 'localStorage' | 'sessionStorage' | 'cookie'
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const AuthContext = createContext<AuthContextType<UserType> | null>(null)
|
|
26
|
+
export function createAuthContext<UserType extends User = User>(
|
|
27
|
+
options: CreateAuthOptions = {}
|
|
28
|
+
) {
|
|
29
|
+
const { storage = 'cookie' } = options
|
|
31
30
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Configure token manager once on mount
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
configureTokenManager({ storage: storageType })
|
|
38
|
-
}, [storageType])
|
|
31
|
+
const AuthContext = createContext<AuthContextType<UserType> | undefined>(
|
|
32
|
+
undefined
|
|
33
|
+
)
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
36
|
+
const configuredRef = useRef(false)
|
|
41
37
|
const manager = useTokenManager()
|
|
42
38
|
|
|
43
|
-
const
|
|
39
|
+
const { user, loading, error, setUser, setLoading, setError, resetAuth } =
|
|
40
|
+
useAuthStore()
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
// Configure token manager once
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!configuredRef.current) {
|
|
45
|
+
configureTokenManager({ storage })
|
|
46
|
+
configuredRef.current = true
|
|
47
|
+
}
|
|
48
|
+
}, [storage])
|
|
48
49
|
|
|
49
|
-
// Restore
|
|
50
|
+
// Restore user from token storage
|
|
50
51
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
useAuthStore.getState().setUser(parsedUser)
|
|
56
|
-
useAuthStore.getState().setError(null)
|
|
57
|
-
} catch {
|
|
58
|
-
useAuthStore.getState().resetAuth()
|
|
59
|
-
useAuthStore.getState().setError(
|
|
60
|
-
new Error('Failed to parse saved user')
|
|
61
|
-
)
|
|
52
|
+
try {
|
|
53
|
+
const savedUser = manager.getSingleToken('user')
|
|
54
|
+
if (savedUser) {
|
|
55
|
+
setUser(JSON.parse(savedUser) as UserType)
|
|
62
56
|
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
setError(err instanceof Error ? err : new Error(String(err)))
|
|
59
|
+
} finally {
|
|
60
|
+
setLoading(false)
|
|
63
61
|
}
|
|
64
|
-
|
|
65
|
-
}, [manager])
|
|
66
|
-
|
|
67
|
-
const setUser = (userData: UserType) => {
|
|
68
|
-
useAuthStore.getState().setUser(userData as any)
|
|
69
|
-
useAuthStore.getState().setError(null)
|
|
70
|
-
manager.setTokens({ user: JSON.stringify(userData) })
|
|
71
|
-
}
|
|
62
|
+
}, [manager, setUser, setLoading, setError])
|
|
72
63
|
|
|
73
64
|
const login = (tokens: Record<string, string>, userData: UserType) => {
|
|
74
65
|
try {
|
|
75
|
-
manager.setTokens(tokens)
|
|
66
|
+
manager.setTokens({ ...tokens, user: JSON.stringify(userData) })
|
|
76
67
|
setUser(userData)
|
|
77
68
|
} catch (err) {
|
|
78
|
-
|
|
79
|
-
err instanceof Error ? err : new Error(String(err))
|
|
80
|
-
)
|
|
69
|
+
setError(err instanceof Error ? err : new Error(String(err)))
|
|
81
70
|
}
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
const logout = () => {
|
|
85
74
|
try {
|
|
86
75
|
manager.clearTokens()
|
|
87
|
-
|
|
76
|
+
resetAuth()
|
|
88
77
|
} catch (err) {
|
|
89
|
-
|
|
90
|
-
err instanceof Error ? err : new Error(String(err))
|
|
91
|
-
)
|
|
78
|
+
setError(err instanceof Error ? err : new Error(String(err)))
|
|
92
79
|
}
|
|
93
80
|
}
|
|
94
81
|
|
|
95
82
|
return (
|
|
96
83
|
<AuthContext.Provider
|
|
97
|
-
value={{
|
|
84
|
+
value={{
|
|
85
|
+
user: user as UserType | null,
|
|
86
|
+
login,
|
|
87
|
+
logout,
|
|
88
|
+
setUser: (u: UserType) => setUser(u),
|
|
89
|
+
loading,
|
|
90
|
+
error,
|
|
91
|
+
}}
|
|
98
92
|
>
|
|
99
93
|
{children}
|
|
100
94
|
</AuthContext.Provider>
|
package/src/RoleGuard.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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 {
|
|
5
|
+
import { useAuthStore } from '../store/useGuardStore'
|
|
6
6
|
|
|
7
7
|
type RoleGuardProps = {
|
|
8
8
|
children: React.ReactNode
|
|
@@ -15,21 +15,37 @@ const RoleGuard = ({
|
|
|
15
15
|
allowedRoles,
|
|
16
16
|
redirectTo = '/unauthorized',
|
|
17
17
|
}: RoleGuardProps) => {
|
|
18
|
-
const { user, loading } = useAuth()
|
|
19
18
|
const router = useRouter()
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
// Get auth state from Zustand
|
|
21
|
+
const { user, isAuthChecked, isAuthenticated } = useAuthStore()
|
|
21
22
|
|
|
22
23
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
+
// Wait until auth is checked
|
|
25
|
+
if (!isAuthChecked) return
|
|
26
|
+
|
|
27
|
+
// Not authenticated
|
|
28
|
+
if (!isAuthenticated || !user) {
|
|
29
|
+
router.replace('/login')
|
|
30
|
+
return
|
|
31
|
+
}
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
// Role check
|
|
34
|
+
const role = user?.role
|
|
35
|
+
if (!allowedRoles.includes(role)) {
|
|
27
36
|
router.replace(redirectTo)
|
|
28
37
|
}
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
}, [isAuthChecked, isAuthenticated, user, allowedRoles, redirectTo, router])
|
|
39
|
+
|
|
40
|
+
// Show loading until auth is checked
|
|
41
|
+
if (!isAuthChecked) return <div>Loading...</div>
|
|
42
|
+
|
|
43
|
+
// Not authenticated
|
|
44
|
+
if (!isAuthenticated || !user) return null
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
// Role not allowed
|
|
47
|
+
const role = (user as any)?.role
|
|
48
|
+
if (!allowedRoles.includes(role)) return null
|
|
33
49
|
|
|
34
50
|
return <>{children}</>
|
|
35
51
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
1
|
'use client'
|
|
3
2
|
|
|
4
3
|
import { createAuthContext } from './AuthProvider'
|
|
4
|
+
|
|
5
5
|
export { default as AuthGuard } from './AuthGuard'
|
|
6
6
|
export { default as RoleGuard } from './RoleGuard'
|
|
7
7
|
|
|
8
8
|
export type User = any
|
|
9
9
|
|
|
10
|
-
// Factory function for app
|
|
11
10
|
export function createAppAuth(
|
|
12
11
|
storage: 'localStorage' | 'sessionStorage' | 'cookie' = 'cookie'
|
|
13
12
|
) {
|
|
14
|
-
return createAuthContext
|
|
13
|
+
return createAuthContext({ storage })
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
// Default instance (optional)
|
|
18
|
-
export const { AuthProvider, useAuth } = createAppAuth()
|
package/store/useGuardStore.ts
CHANGED
|
@@ -7,13 +7,15 @@ export type User = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
type AuthState = {
|
|
10
|
-
user: User | null
|
|
10
|
+
user: User | null // user can be null
|
|
11
11
|
isAuthenticated: boolean
|
|
12
12
|
isAuthChecked: boolean
|
|
13
|
+
loading: boolean // new loading state
|
|
13
14
|
error: Error | null
|
|
14
15
|
setUser: (user: User | null) => void
|
|
15
16
|
setAuth: (isAuth: boolean) => void
|
|
16
17
|
setAuthChecked: (checked: boolean) => void
|
|
18
|
+
setLoading: (loading: boolean) => void
|
|
17
19
|
setError: (err: Error | null) => void
|
|
18
20
|
resetAuth: () => void
|
|
19
21
|
}
|
|
@@ -22,11 +24,13 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|
|
22
24
|
user: null,
|
|
23
25
|
isAuthenticated: false,
|
|
24
26
|
isAuthChecked: false,
|
|
27
|
+
loading: true, // start as loading
|
|
25
28
|
error: null,
|
|
26
29
|
|
|
27
30
|
setUser: (user) => set({ user }),
|
|
28
31
|
setAuth: (isAuth) => set({ isAuthenticated: isAuth }),
|
|
29
32
|
setAuthChecked: (checked) => set({ isAuthChecked: checked }),
|
|
33
|
+
setLoading: (loading) => set({ loading }),
|
|
30
34
|
setError: (err) => set({ error: err }),
|
|
31
35
|
|
|
32
36
|
resetAuth: () =>
|
|
@@ -34,6 +38,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
|
|
34
38
|
user: null,
|
|
35
39
|
isAuthenticated: false,
|
|
36
40
|
isAuthChecked: false,
|
|
41
|
+
loading: false,
|
|
37
42
|
error: null,
|
|
38
43
|
}),
|
|
39
44
|
}))
|