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