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