@monocloud/auth-nextjs 0.1.5 → 0.1.7
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/README.md +21 -4
- package/dist/{chunk-CbDLau6x.cjs → chunk-C0xms8kb.cjs} +1 -1
- package/dist/client/index.cjs +4 -3
- package/dist/client/index.d.mts +73 -118
- package/dist/client/index.mjs +2 -2
- package/dist/components/client/index.cjs +46 -88
- package/dist/components/client/index.cjs.map +1 -1
- package/dist/components/client/index.d.mts +59 -93
- package/dist/components/client/index.mjs +42 -85
- package/dist/components/client/index.mjs.map +1 -1
- package/dist/components/index.cjs +46 -42
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.mts +72 -49
- package/dist/components/index.mjs +44 -41
- package/dist/components/index.mjs.map +1 -1
- package/dist/index.cjs +399 -374
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +1546 -1835
- package/dist/index.mjs +393 -380
- package/dist/index.mjs.map +1 -1
- package/dist/{client-xfBYYato.cjs → protect-client-page-B1fOU4Zl.cjs} +65 -114
- package/dist/protect-client-page-B1fOU4Zl.cjs.map +1 -0
- package/dist/{client-D-3RMRNY.mjs → protect-client-page-BFlGkK8F.mjs} +63 -112
- package/dist/protect-client-page-BFlGkK8F.mjs.map +1 -0
- package/dist/types-xS_Me3Qg.d.mts +563 -0
- package/package.json +8 -7
- package/dist/client-D-3RMRNY.mjs.map +0 -1
- package/dist/client-xfBYYato.cjs.map +0 -1
- package/dist/types-CsBjAJce.d.mts +0 -410
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_chunk = require('./chunk-
|
|
1
|
+
const require_chunk = require('./chunk-C0xms8kb.cjs');
|
|
2
2
|
let _monocloud_auth_node_core_utils = require("@monocloud/auth-node-core/utils");
|
|
3
3
|
let swr = require("swr");
|
|
4
4
|
swr = require_chunk.__toESM(swr);
|
|
@@ -14,32 +14,33 @@ const fetchUser = async (url) => {
|
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* `useAuth()` is a client-side hook that provides access to the current authentication state.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* It can only be used inside **Client Components**.
|
|
20
20
|
*
|
|
21
|
-
* @example
|
|
22
|
-
*
|
|
23
|
-
* ```tsx
|
|
21
|
+
* @example Basic Usage
|
|
22
|
+
* ```tsx title="Basic Usage"
|
|
24
23
|
* "use client";
|
|
25
24
|
*
|
|
26
25
|
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
27
26
|
*
|
|
28
27
|
* export default function Home() {
|
|
29
|
-
* const { user } = useAuth();
|
|
28
|
+
* const { user, isAuthenticated } = useAuth();
|
|
29
|
+
*
|
|
30
|
+
* if (!isAuthenticated) {
|
|
31
|
+
* return <>Not signed in</>;
|
|
32
|
+
* }
|
|
30
33
|
*
|
|
31
34
|
* return <>User Id: {user?.sub}</>;
|
|
32
35
|
* }
|
|
33
36
|
* ```
|
|
34
37
|
*
|
|
35
|
-
* @example
|
|
38
|
+
* @example Refetch user
|
|
36
39
|
*
|
|
37
|
-
* Calling `refetch(true)`
|
|
38
|
-
*
|
|
40
|
+
* Calling `refetch(true)` forces a refresh of the user profile from the UserInfo endpoint.
|
|
41
|
+
* Calling `refetch()` refreshes authentication state without forcing a UserInfo request.
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* ```tsx
|
|
43
|
+
* ```tsx title="Refetch User"
|
|
43
44
|
* "use client";
|
|
44
45
|
*
|
|
45
46
|
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
@@ -49,47 +50,16 @@ const fetchUser = async (url) => {
|
|
|
49
50
|
*
|
|
50
51
|
* return (
|
|
51
52
|
* <>
|
|
52
|
-
* <pre>{JSON.stringify(user)}</pre>
|
|
53
|
-
* <button onClick={() => refetch(true)}>Refresh</button>
|
|
53
|
+
* <pre>{JSON.stringify(user, null, 2)}</pre>
|
|
54
|
+
* <button onClick={() => refetch(true)}>Refresh Profile</button>
|
|
54
55
|
* </>
|
|
55
56
|
* );
|
|
56
57
|
* }
|
|
57
58
|
* ```
|
|
58
59
|
*
|
|
59
|
-
* @
|
|
60
|
-
*
|
|
61
|
-
* ```tsx
|
|
62
|
-
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
63
|
-
*
|
|
64
|
-
* export default function Home() {
|
|
65
|
-
* const { user } = useAuth();
|
|
66
|
-
*
|
|
67
|
-
* return <>User Id: {user?.sub}</>;
|
|
68
|
-
* }
|
|
69
|
-
* ```
|
|
70
|
-
*
|
|
71
|
-
* @example Pages Router - Refetch user from Userinfo endpoint
|
|
72
|
-
*
|
|
73
|
-
* Calling `refetch(true)` will force refresh the user's profile from the userinfo endpoint.
|
|
74
|
-
* If you do not intent to refersh from your tenants userinfo endpoint, use just `refetch()`
|
|
75
|
-
*
|
|
76
|
-
* **Note⚠️: You need to set the env `MONOCLOUD_AUTH_ALLOW_QUERY_PARAM_OVERRIDES=true` or `allowQueryParamOverrides` should be `true` in the client initialization for force refresh to work**
|
|
77
|
-
*
|
|
78
|
-
* ```tsx
|
|
79
|
-
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
80
|
-
*
|
|
81
|
-
* export default function Home() {
|
|
82
|
-
* const { user, refetch } = useAuth();
|
|
83
|
-
*
|
|
84
|
-
* return (
|
|
85
|
-
* <>
|
|
86
|
-
* <pre>{JSON.stringify(user)}</pre>
|
|
87
|
-
* <button onClick={() => refetch(true)}>Refresh</button>
|
|
88
|
-
* </>
|
|
89
|
-
* );
|
|
90
|
-
* }
|
|
91
|
-
* ```
|
|
60
|
+
* @returns
|
|
92
61
|
*
|
|
62
|
+
* @category Hooks
|
|
93
63
|
*/
|
|
94
64
|
const useAuth = () => {
|
|
95
65
|
const key = process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_USER_INFO_URL ?? `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/api/auth/userinfo`;
|
|
@@ -123,7 +93,7 @@ const useAuth = () => {
|
|
|
123
93
|
};
|
|
124
94
|
|
|
125
95
|
//#endregion
|
|
126
|
-
//#region src/client/protect.tsx
|
|
96
|
+
//#region src/client/protect-client-page.tsx
|
|
127
97
|
const redirectToSignIn = (options) => {
|
|
128
98
|
const searchParams = new URLSearchParams(window.location.search);
|
|
129
99
|
searchParams.set("return_url", options.returnUrl ?? window.location.toString());
|
|
@@ -145,110 +115,91 @@ const handlePageError = (error, options) => {
|
|
|
145
115
|
throw error;
|
|
146
116
|
};
|
|
147
117
|
/**
|
|
148
|
-
*
|
|
149
|
-
* Ensures that only authenticated users can access the component.
|
|
150
|
-
*
|
|
151
|
-
* **Note⚠️: Since `window.location` is set as `returnUrl` query param by default, you need to set the env `MONOCLOUD_AUTH_ALLOW_QUERY_PARAM_OVERRIDES=true` or `allowQueryParamOverrides` should be `true` in the client initialization for returning to the same page.**
|
|
118
|
+
* `protectClientPage()` wraps a **client-rendered page component** and ensures that only authenticated users can access it.
|
|
152
119
|
*
|
|
153
|
-
*
|
|
154
|
-
* @param options - The options.
|
|
120
|
+
* If the user is authenticated, the wrapped component receives a `user` prop.
|
|
155
121
|
*
|
|
156
|
-
*
|
|
122
|
+
* > This function runs on the client and controls rendering only.
|
|
123
|
+
* > To enforce access before rendering (server-side), use the server {@link MonoCloudNextClient.protectPage | protectPage()} method on {@link MonoCloudNextClient}.
|
|
157
124
|
*
|
|
158
|
-
* @example
|
|
125
|
+
* @example Basic Usage
|
|
159
126
|
*
|
|
160
|
-
* ```tsx
|
|
127
|
+
* ```tsx title="Basic Usage"
|
|
161
128
|
* "use client";
|
|
162
129
|
*
|
|
163
|
-
* import {
|
|
130
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
164
131
|
*
|
|
165
|
-
* export default
|
|
166
|
-
* return <>
|
|
132
|
+
* export default protectClientPage(function Home({ user }) {
|
|
133
|
+
* return <>Signed in as {user.email}</>;
|
|
167
134
|
* });
|
|
168
135
|
* ```
|
|
169
136
|
*
|
|
170
|
-
* @example
|
|
171
|
-
*
|
|
172
|
-
* See {@link ProtectPageOptions} for more options.
|
|
137
|
+
* @example With Options
|
|
173
138
|
*
|
|
174
|
-
* ```tsx
|
|
139
|
+
* ```tsx title="With Options"
|
|
175
140
|
* "use client";
|
|
176
141
|
*
|
|
177
|
-
* import {
|
|
142
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
178
143
|
*
|
|
179
|
-
* export default
|
|
180
|
-
* function Home() {
|
|
181
|
-
* return <>
|
|
144
|
+
* export default protectClientPage(
|
|
145
|
+
* function Home({ user }) {
|
|
146
|
+
* return <>Signed in as {user.email}</>;
|
|
182
147
|
* },
|
|
183
|
-
* {
|
|
148
|
+
* {
|
|
149
|
+
* returnUrl: "/dashboard",
|
|
150
|
+
* authParams: { loginHint: "user@example.com" }
|
|
151
|
+
* }
|
|
184
152
|
* );
|
|
185
153
|
* ```
|
|
186
|
-
* @example Custom Fallback
|
|
187
154
|
*
|
|
188
|
-
*
|
|
155
|
+
* @example Custom access denied UI
|
|
156
|
+
*
|
|
157
|
+
* ```tsx title="Custom access denied UI"
|
|
189
158
|
* "use client";
|
|
190
159
|
*
|
|
191
|
-
* import {
|
|
160
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
192
161
|
*
|
|
193
|
-
* export default
|
|
194
|
-
* function Home() {
|
|
195
|
-
* return <>
|
|
162
|
+
* export default protectClientPage(
|
|
163
|
+
* function Home({ user }) {
|
|
164
|
+
* return <>Signed in as {user.email}</>;
|
|
196
165
|
* },
|
|
197
166
|
* {
|
|
198
|
-
*
|
|
167
|
+
* onAccessDenied: () => <div>Please sign in to continue</div>
|
|
199
168
|
* }
|
|
200
169
|
* );
|
|
201
170
|
* ```
|
|
202
171
|
*
|
|
203
|
-
* @example Group
|
|
172
|
+
* @example Group protection
|
|
204
173
|
*
|
|
205
|
-
* ```tsx
|
|
174
|
+
* ```tsx title="Group protection"
|
|
206
175
|
* "use client";
|
|
207
176
|
*
|
|
208
|
-
* import {
|
|
177
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
209
178
|
*
|
|
210
|
-
* export default
|
|
211
|
-
* function Home() {
|
|
212
|
-
* return <>Welcome Admin</>;
|
|
179
|
+
* export default protectClientPage(
|
|
180
|
+
* function Home({ user }) {
|
|
181
|
+
* return <>Welcome Admin {user.email}</>;
|
|
213
182
|
* },
|
|
214
183
|
* {
|
|
215
184
|
* groups: ["admin"],
|
|
216
|
-
*
|
|
185
|
+
* onGroupAccessDenied: (user) => <div>User {user.email} is not an admin</div>
|
|
217
186
|
* }
|
|
218
187
|
* );
|
|
219
188
|
* ```
|
|
220
189
|
*
|
|
221
|
-
* @
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* import { protectPage } from "@monocloud/auth-nextjs/client";
|
|
225
|
-
*
|
|
226
|
-
* export default protectPage(function Home() {
|
|
227
|
-
* return <>You are signed in</>;
|
|
228
|
-
* });
|
|
229
|
-
* ```
|
|
230
|
-
*
|
|
231
|
-
* @example Pages Router with options
|
|
232
|
-
*
|
|
233
|
-
* See {@link ProtectPageOptions} for more options.
|
|
190
|
+
* @param Component - The page component to protect
|
|
191
|
+
* @param options - Optional configuration
|
|
192
|
+
* @returns A protected React component.
|
|
234
193
|
*
|
|
235
|
-
*
|
|
236
|
-
* import { protectPage } from "@monocloud/auth-nextjs/client";
|
|
194
|
+
* @category Functions
|
|
237
195
|
*
|
|
238
|
-
* export default protectPage(
|
|
239
|
-
* function Home() {
|
|
240
|
-
* return <>You are signed in</>;
|
|
241
|
-
* },
|
|
242
|
-
* { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
|
|
243
|
-
* );
|
|
244
|
-
* ```
|
|
245
196
|
*/
|
|
246
|
-
const
|
|
197
|
+
const protectClientPage = (Component, options) => {
|
|
247
198
|
return (props) => {
|
|
248
199
|
const { user, error, isLoading } = useAuth();
|
|
249
200
|
(0, react.useEffect)(() => {
|
|
250
201
|
if (!user && !isLoading && !error) {
|
|
251
|
-
if (options === null || options === void 0 ? void 0 : options.
|
|
202
|
+
if (options === null || options === void 0 ? void 0 : options.onAccessDenied) return;
|
|
252
203
|
const authParams = (options === null || options === void 0 ? void 0 : options.authParams) ?? {};
|
|
253
204
|
redirectToSignIn({
|
|
254
205
|
returnUrl: options === null || options === void 0 ? void 0 : options.returnUrl,
|
|
@@ -261,11 +212,11 @@ const protectPage = (Component, options) => {
|
|
|
261
212
|
error
|
|
262
213
|
]);
|
|
263
214
|
if (error) return handlePageError(error, options);
|
|
264
|
-
if (!user && !isLoading && (options === null || options === void 0 ? void 0 : options.
|
|
215
|
+
if (!user && !isLoading && (options === null || options === void 0 ? void 0 : options.onAccessDenied)) return options.onAccessDenied();
|
|
265
216
|
if (user) {
|
|
266
217
|
if ((options === null || options === void 0 ? void 0 : options.groups) && !(0, _monocloud_auth_node_core_utils.isUserInGroup)(user, options.groups, options.groupsClaim ?? process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_GROUPS_CLAIM, options.matchAll)) {
|
|
267
|
-
const {
|
|
268
|
-
return
|
|
218
|
+
const { onGroupAccessDenied = () => /* @__PURE__ */ react.default.createElement("div", null, "Access Denied") } = options;
|
|
219
|
+
return onGroupAccessDenied(user);
|
|
269
220
|
}
|
|
270
221
|
return /* @__PURE__ */ react.default.createElement(Component, {
|
|
271
222
|
user,
|
|
@@ -277,10 +228,10 @@ const protectPage = (Component, options) => {
|
|
|
277
228
|
};
|
|
278
229
|
|
|
279
230
|
//#endregion
|
|
280
|
-
Object.defineProperty(exports, '
|
|
231
|
+
Object.defineProperty(exports, 'protectClientPage', {
|
|
281
232
|
enumerable: true,
|
|
282
233
|
get: function () {
|
|
283
|
-
return
|
|
234
|
+
return protectClientPage;
|
|
284
235
|
}
|
|
285
236
|
});
|
|
286
237
|
Object.defineProperty(exports, 'redirectToSignIn', {
|
|
@@ -295,4 +246,4 @@ Object.defineProperty(exports, 'useAuth', {
|
|
|
295
246
|
return useAuth;
|
|
296
247
|
}
|
|
297
248
|
});
|
|
298
|
-
//# sourceMappingURL=client-
|
|
249
|
+
//# sourceMappingURL=protect-client-page-B1fOU4Zl.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protect-client-page-B1fOU4Zl.cjs","names":[],"sources":["../src/client/use-auth.tsx","../src/client/protect-client-page.tsx"],"sourcesContent":["'use client';\n\nimport type { MonoCloudUser } from '@monocloud/auth-node-core';\nimport useSWR from 'swr';\n\n/**\n * Authentication State returned by `useAuth` hook.\n *\n * @category Types\n */\nexport interface AuthenticationState {\n /**\n * Flag indicating if the authentication state is still loading.\n */\n isLoading: boolean;\n /**\n * Flag indicating if the user is authenticated.\n */\n isAuthenticated: boolean;\n /**\n * Error encountered during authentication, if any.\n */\n error?: Error;\n /**\n * The authenticated user's information, if available.\n */\n user?: MonoCloudUser;\n /**\n * Function to refetch the authentication state.\n *\n */\n refetch: (refresh?: boolean) => void;\n}\n\nconst fetchUser = async (url: string): Promise<MonoCloudUser | undefined> => {\n const res = await fetch(url, { credentials: 'include' });\n\n if (res.status === 204) {\n return undefined;\n }\n\n if (res.ok) {\n return res.json();\n }\n\n throw new Error('Failed to fetch user');\n};\n\n/**\n *\n * `useAuth()` is a client-side hook that provides access to the current authentication state.\n *\n * It can only be used inside **Client Components**.\n *\n * @example Basic Usage\n * ```tsx title=\"Basic Usage\"\n * \"use client\";\n *\n * import { useAuth } from \"@monocloud/auth-nextjs/client\";\n *\n * export default function Home() {\n * const { user, isAuthenticated } = useAuth();\n *\n * if (!isAuthenticated) {\n * return <>Not signed in</>;\n * }\n *\n * return <>User Id: {user?.sub}</>;\n * }\n * ```\n *\n * @example Refetch user\n *\n * Calling `refetch(true)` forces a refresh of the user profile from the UserInfo endpoint.\n * Calling `refetch()` refreshes authentication state without forcing a UserInfo request.\n *\n * ```tsx title=\"Refetch User\"\n * \"use client\";\n *\n * import { useAuth } from \"@monocloud/auth-nextjs/client\";\n *\n * export default function Home() {\n * const { user, refetch } = useAuth();\n *\n * return (\n * <>\n * <pre>{JSON.stringify(user, null, 2)}</pre>\n * <button onClick={() => refetch(true)}>Refresh Profile</button>\n * </>\n * );\n * }\n * ```\n *\n * @returns\n *\n * @category Hooks\n */\nexport const useAuth = (): AuthenticationState => {\n const key =\n process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_USER_INFO_URL ??\n // eslint-disable-next-line no-underscore-dangle\n `${process.env.__NEXT_ROUTER_BASEPATH ?? ''}/api/auth/userinfo`;\n\n const { data, error, isLoading, mutate } = useSWR<MonoCloudUser | undefined>(\n key,\n fetchUser\n );\n\n const refetch = (refresh?: boolean): void => {\n const url = new URL(key, 'https://dummy');\n if (refresh) {\n url.searchParams.set('refresh', 'true');\n }\n\n void mutate(async () => await fetchUser(url.pathname + url.search), {\n revalidate: false,\n });\n };\n\n if (error) {\n return {\n user: undefined,\n isLoading: false,\n isAuthenticated: false,\n error: error as Error,\n refetch,\n };\n }\n\n if (data) {\n return {\n user: data,\n isLoading,\n isAuthenticated: !!data && Object.keys(data).length > 0,\n error: undefined,\n refetch,\n };\n }\n\n return {\n user: undefined,\n isLoading,\n isAuthenticated: false,\n error: undefined,\n /* v8 ignore next -- @preserve */\n refetch: (): void => {},\n };\n};\n","/* eslint-disable react/display-name */\n'use client';\n\nimport React, { ComponentType, useEffect } from 'react';\nimport type { MonoCloudUser } from '@monocloud/auth-node-core';\nimport { isUserInGroup } from '@monocloud/auth-node-core/utils';\nimport { useAuth } from './use-auth';\nimport { ExtraAuthParams, GroupOptions } from '../types';\nimport type { MonoCloudNextClient } from '../monocloud-next-client';\n\n/**\n * Options for configuring page protection.\n *\n * @category Types\n */\nexport interface ProtectClientPageOptions extends GroupOptions {\n /**\n * The URL where the user will be redirected to after sign in.\n */\n returnUrl?: string;\n\n /**\n * A custom react element to render when the user is not authenticated.\n */\n onAccessDenied?: () => React.ReactNode;\n\n /**\n * A custom react element to render when the user is authenticated but does not belong to the required groups.\n */\n onGroupAccessDenied?: (user: MonoCloudUser) => React.ReactNode;\n\n /**\n * Authorization parameters to be used during authentication.\n */\n authParams?: ExtraAuthParams;\n\n /**\n * Callback function to handle errors.\n * If not provided, errors will be thrown.\n *\n * @param error - The error object.\n * @returns JSX element to handle the error.\n */\n onError?: (error: Error) => React.ReactNode;\n}\n\nexport const redirectToSignIn = (\n options: { returnUrl?: string } & ExtraAuthParams\n): void => {\n const searchParams = new URLSearchParams(window.location.search);\n searchParams.set(\n 'return_url',\n options.returnUrl ?? window.location.toString()\n );\n\n if (options?.scopes) {\n searchParams.set('scope', options.scopes);\n }\n if (options?.resource) {\n searchParams.set('resource', options.resource);\n }\n\n if (options?.acrValues) {\n searchParams.set('acr_values', options.acrValues.join(' '));\n }\n\n if (options?.display) {\n searchParams.set('display', options.display);\n }\n\n if (options?.prompt) {\n searchParams.set('prompt', options.prompt);\n }\n\n if (options?.authenticatorHint) {\n searchParams.set('authenticator_hint', options.authenticatorHint);\n }\n\n if (options?.uiLocales) {\n searchParams.set('ui_locales', options.uiLocales);\n }\n\n if (options?.maxAge) {\n searchParams.set('max_age', options.maxAge.toString());\n }\n\n if (options?.loginHint) {\n searchParams.set('login_hint', options.loginHint);\n }\n\n window.location.assign(\n // eslint-disable-next-line no-underscore-dangle\n `${process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_SIGNIN_URL ?? `${process.env.__NEXT_ROUTER_BASEPATH ?? ''}/api/auth/signin`}?${searchParams.toString()}`\n );\n};\n\nconst handlePageError = (\n error: Error,\n options?: ProtectClientPageOptions\n): React.ReactNode => {\n /* v8 ignore else -- @preserve */\n if (options?.onError) {\n return options.onError(error);\n }\n\n /* v8 ignore next -- @preserve */\n throw error;\n};\n\n/**\n * `protectClientPage()` wraps a **client-rendered page component** and ensures that only authenticated users can access it.\n *\n * If the user is authenticated, the wrapped component receives a `user` prop.\n *\n * > This function runs on the client and controls rendering only.\n * > To enforce access before rendering (server-side), use the server {@link MonoCloudNextClient.protectPage | protectPage()} method on {@link MonoCloudNextClient}.\n *\n * @example Basic Usage\n *\n * ```tsx title=\"Basic Usage\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * });\n * ```\n *\n * @example With Options\n *\n * ```tsx title=\"With Options\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * },\n * {\n * returnUrl: \"/dashboard\",\n * authParams: { loginHint: \"user@example.com\" }\n * }\n * );\n * ```\n *\n * @example Custom access denied UI\n *\n * ```tsx title=\"Custom access denied UI\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * },\n * {\n * onAccessDenied: () => <div>Please sign in to continue</div>\n * }\n * );\n * ```\n *\n * @example Group protection\n *\n * ```tsx title=\"Group protection\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Welcome Admin {user.email}</>;\n * },\n * {\n * groups: [\"admin\"],\n * onGroupAccessDenied: (user) => <div>User {user.email} is not an admin</div>\n * }\n * );\n * ```\n *\n * @param Component - The page component to protect\n * @param options - Optional configuration\n * @returns A protected React component.\n *\n * @category Functions\n *\n */\nexport const protectClientPage = <P extends object>(\n Component: ComponentType<P & { user: MonoCloudUser }>,\n options?: ProtectClientPageOptions\n): React.FC<P> => {\n return props => {\n const { user, error, isLoading } = useAuth();\n\n useEffect(() => {\n if (!user && !isLoading && !error) {\n if (options?.onAccessDenied) {\n return;\n }\n\n const authParams = options?.authParams ?? {};\n redirectToSignIn({\n returnUrl: options?.returnUrl,\n ...authParams,\n });\n }\n }, [user, isLoading, error]);\n\n if (error) {\n return handlePageError(error, options);\n }\n\n if (!user && !isLoading && options?.onAccessDenied) {\n return options.onAccessDenied();\n }\n\n if (user) {\n if (\n options?.groups &&\n !isUserInGroup(\n user,\n options.groups,\n options.groupsClaim ??\n process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_GROUPS_CLAIM,\n options.matchAll\n )\n ) {\n const {\n onGroupAccessDenied = (): React.ReactNode => <div>Access Denied</div>,\n } = options;\n return onGroupAccessDenied(user);\n }\n\n return <Component user={user} {...props} />;\n }\n\n return null;\n };\n};\n"],"mappings":"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"}
|
|
@@ -11,32 +11,33 @@ const fetchUser = async (url) => {
|
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* `useAuth()` is a client-side hook that provides access to the current authentication state.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* It can only be used inside **Client Components**.
|
|
17
17
|
*
|
|
18
|
-
* @example
|
|
19
|
-
*
|
|
20
|
-
* ```tsx
|
|
18
|
+
* @example Basic Usage
|
|
19
|
+
* ```tsx title="Basic Usage"
|
|
21
20
|
* "use client";
|
|
22
21
|
*
|
|
23
22
|
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
24
23
|
*
|
|
25
24
|
* export default function Home() {
|
|
26
|
-
* const { user } = useAuth();
|
|
25
|
+
* const { user, isAuthenticated } = useAuth();
|
|
26
|
+
*
|
|
27
|
+
* if (!isAuthenticated) {
|
|
28
|
+
* return <>Not signed in</>;
|
|
29
|
+
* }
|
|
27
30
|
*
|
|
28
31
|
* return <>User Id: {user?.sub}</>;
|
|
29
32
|
* }
|
|
30
33
|
* ```
|
|
31
34
|
*
|
|
32
|
-
* @example
|
|
35
|
+
* @example Refetch user
|
|
33
36
|
*
|
|
34
|
-
* Calling `refetch(true)`
|
|
35
|
-
*
|
|
37
|
+
* Calling `refetch(true)` forces a refresh of the user profile from the UserInfo endpoint.
|
|
38
|
+
* Calling `refetch()` refreshes authentication state without forcing a UserInfo request.
|
|
36
39
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* ```tsx
|
|
40
|
+
* ```tsx title="Refetch User"
|
|
40
41
|
* "use client";
|
|
41
42
|
*
|
|
42
43
|
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
@@ -46,47 +47,16 @@ const fetchUser = async (url) => {
|
|
|
46
47
|
*
|
|
47
48
|
* return (
|
|
48
49
|
* <>
|
|
49
|
-
* <pre>{JSON.stringify(user)}</pre>
|
|
50
|
-
* <button onClick={() => refetch(true)}>Refresh</button>
|
|
50
|
+
* <pre>{JSON.stringify(user, null, 2)}</pre>
|
|
51
|
+
* <button onClick={() => refetch(true)}>Refresh Profile</button>
|
|
51
52
|
* </>
|
|
52
53
|
* );
|
|
53
54
|
* }
|
|
54
55
|
* ```
|
|
55
56
|
*
|
|
56
|
-
* @
|
|
57
|
-
*
|
|
58
|
-
* ```tsx
|
|
59
|
-
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
60
|
-
*
|
|
61
|
-
* export default function Home() {
|
|
62
|
-
* const { user } = useAuth();
|
|
63
|
-
*
|
|
64
|
-
* return <>User Id: {user?.sub}</>;
|
|
65
|
-
* }
|
|
66
|
-
* ```
|
|
67
|
-
*
|
|
68
|
-
* @example Pages Router - Refetch user from Userinfo endpoint
|
|
69
|
-
*
|
|
70
|
-
* Calling `refetch(true)` will force refresh the user's profile from the userinfo endpoint.
|
|
71
|
-
* If you do not intent to refersh from your tenants userinfo endpoint, use just `refetch()`
|
|
72
|
-
*
|
|
73
|
-
* **Note⚠️: You need to set the env `MONOCLOUD_AUTH_ALLOW_QUERY_PARAM_OVERRIDES=true` or `allowQueryParamOverrides` should be `true` in the client initialization for force refresh to work**
|
|
74
|
-
*
|
|
75
|
-
* ```tsx
|
|
76
|
-
* import { useAuth } from "@monocloud/auth-nextjs/client";
|
|
77
|
-
*
|
|
78
|
-
* export default function Home() {
|
|
79
|
-
* const { user, refetch } = useAuth();
|
|
80
|
-
*
|
|
81
|
-
* return (
|
|
82
|
-
* <>
|
|
83
|
-
* <pre>{JSON.stringify(user)}</pre>
|
|
84
|
-
* <button onClick={() => refetch(true)}>Refresh</button>
|
|
85
|
-
* </>
|
|
86
|
-
* );
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
57
|
+
* @returns
|
|
89
58
|
*
|
|
59
|
+
* @category Hooks
|
|
90
60
|
*/
|
|
91
61
|
const useAuth = () => {
|
|
92
62
|
const key = process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_USER_INFO_URL ?? `${process.env.__NEXT_ROUTER_BASEPATH ?? ""}/api/auth/userinfo`;
|
|
@@ -120,7 +90,7 @@ const useAuth = () => {
|
|
|
120
90
|
};
|
|
121
91
|
|
|
122
92
|
//#endregion
|
|
123
|
-
//#region src/client/protect.tsx
|
|
93
|
+
//#region src/client/protect-client-page.tsx
|
|
124
94
|
const redirectToSignIn = (options) => {
|
|
125
95
|
const searchParams = new URLSearchParams(window.location.search);
|
|
126
96
|
searchParams.set("return_url", options.returnUrl ?? window.location.toString());
|
|
@@ -142,110 +112,91 @@ const handlePageError = (error, options) => {
|
|
|
142
112
|
throw error;
|
|
143
113
|
};
|
|
144
114
|
/**
|
|
145
|
-
*
|
|
146
|
-
* Ensures that only authenticated users can access the component.
|
|
147
|
-
*
|
|
148
|
-
* **Note⚠️: Since `window.location` is set as `returnUrl` query param by default, you need to set the env `MONOCLOUD_AUTH_ALLOW_QUERY_PARAM_OVERRIDES=true` or `allowQueryParamOverrides` should be `true` in the client initialization for returning to the same page.**
|
|
115
|
+
* `protectClientPage()` wraps a **client-rendered page component** and ensures that only authenticated users can access it.
|
|
149
116
|
*
|
|
150
|
-
*
|
|
151
|
-
* @param options - The options.
|
|
117
|
+
* If the user is authenticated, the wrapped component receives a `user` prop.
|
|
152
118
|
*
|
|
153
|
-
*
|
|
119
|
+
* > This function runs on the client and controls rendering only.
|
|
120
|
+
* > To enforce access before rendering (server-side), use the server {@link MonoCloudNextClient.protectPage | protectPage()} method on {@link MonoCloudNextClient}.
|
|
154
121
|
*
|
|
155
|
-
* @example
|
|
122
|
+
* @example Basic Usage
|
|
156
123
|
*
|
|
157
|
-
* ```tsx
|
|
124
|
+
* ```tsx title="Basic Usage"
|
|
158
125
|
* "use client";
|
|
159
126
|
*
|
|
160
|
-
* import {
|
|
127
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
161
128
|
*
|
|
162
|
-
* export default
|
|
163
|
-
* return <>
|
|
129
|
+
* export default protectClientPage(function Home({ user }) {
|
|
130
|
+
* return <>Signed in as {user.email}</>;
|
|
164
131
|
* });
|
|
165
132
|
* ```
|
|
166
133
|
*
|
|
167
|
-
* @example
|
|
168
|
-
*
|
|
169
|
-
* See {@link ProtectPageOptions} for more options.
|
|
134
|
+
* @example With Options
|
|
170
135
|
*
|
|
171
|
-
* ```tsx
|
|
136
|
+
* ```tsx title="With Options"
|
|
172
137
|
* "use client";
|
|
173
138
|
*
|
|
174
|
-
* import {
|
|
139
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
175
140
|
*
|
|
176
|
-
* export default
|
|
177
|
-
* function Home() {
|
|
178
|
-
* return <>
|
|
141
|
+
* export default protectClientPage(
|
|
142
|
+
* function Home({ user }) {
|
|
143
|
+
* return <>Signed in as {user.email}</>;
|
|
179
144
|
* },
|
|
180
|
-
* {
|
|
145
|
+
* {
|
|
146
|
+
* returnUrl: "/dashboard",
|
|
147
|
+
* authParams: { loginHint: "user@example.com" }
|
|
148
|
+
* }
|
|
181
149
|
* );
|
|
182
150
|
* ```
|
|
183
|
-
* @example Custom Fallback
|
|
184
151
|
*
|
|
185
|
-
*
|
|
152
|
+
* @example Custom access denied UI
|
|
153
|
+
*
|
|
154
|
+
* ```tsx title="Custom access denied UI"
|
|
186
155
|
* "use client";
|
|
187
156
|
*
|
|
188
|
-
* import {
|
|
157
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
189
158
|
*
|
|
190
|
-
* export default
|
|
191
|
-
* function Home() {
|
|
192
|
-
* return <>
|
|
159
|
+
* export default protectClientPage(
|
|
160
|
+
* function Home({ user }) {
|
|
161
|
+
* return <>Signed in as {user.email}</>;
|
|
193
162
|
* },
|
|
194
163
|
* {
|
|
195
|
-
*
|
|
164
|
+
* onAccessDenied: () => <div>Please sign in to continue</div>
|
|
196
165
|
* }
|
|
197
166
|
* );
|
|
198
167
|
* ```
|
|
199
168
|
*
|
|
200
|
-
* @example Group
|
|
169
|
+
* @example Group protection
|
|
201
170
|
*
|
|
202
|
-
* ```tsx
|
|
171
|
+
* ```tsx title="Group protection"
|
|
203
172
|
* "use client";
|
|
204
173
|
*
|
|
205
|
-
* import {
|
|
174
|
+
* import { protectClientPage } from "@monocloud/auth-nextjs/client";
|
|
206
175
|
*
|
|
207
|
-
* export default
|
|
208
|
-
* function Home() {
|
|
209
|
-
* return <>Welcome Admin</>;
|
|
176
|
+
* export default protectClientPage(
|
|
177
|
+
* function Home({ user }) {
|
|
178
|
+
* return <>Welcome Admin {user.email}</>;
|
|
210
179
|
* },
|
|
211
180
|
* {
|
|
212
181
|
* groups: ["admin"],
|
|
213
|
-
*
|
|
182
|
+
* onGroupAccessDenied: (user) => <div>User {user.email} is not an admin</div>
|
|
214
183
|
* }
|
|
215
184
|
* );
|
|
216
185
|
* ```
|
|
217
186
|
*
|
|
218
|
-
* @
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* import { protectPage } from "@monocloud/auth-nextjs/client";
|
|
222
|
-
*
|
|
223
|
-
* export default protectPage(function Home() {
|
|
224
|
-
* return <>You are signed in</>;
|
|
225
|
-
* });
|
|
226
|
-
* ```
|
|
227
|
-
*
|
|
228
|
-
* @example Pages Router with options
|
|
229
|
-
*
|
|
230
|
-
* See {@link ProtectPageOptions} for more options.
|
|
187
|
+
* @param Component - The page component to protect
|
|
188
|
+
* @param options - Optional configuration
|
|
189
|
+
* @returns A protected React component.
|
|
231
190
|
*
|
|
232
|
-
*
|
|
233
|
-
* import { protectPage } from "@monocloud/auth-nextjs/client";
|
|
191
|
+
* @category Functions
|
|
234
192
|
*
|
|
235
|
-
* export default protectPage(
|
|
236
|
-
* function Home() {
|
|
237
|
-
* return <>You are signed in</>;
|
|
238
|
-
* },
|
|
239
|
-
* { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
|
|
240
|
-
* );
|
|
241
|
-
* ```
|
|
242
193
|
*/
|
|
243
|
-
const
|
|
194
|
+
const protectClientPage = (Component, options) => {
|
|
244
195
|
return (props) => {
|
|
245
196
|
const { user, error, isLoading } = useAuth();
|
|
246
197
|
useEffect(() => {
|
|
247
198
|
if (!user && !isLoading && !error) {
|
|
248
|
-
if (options === null || options === void 0 ? void 0 : options.
|
|
199
|
+
if (options === null || options === void 0 ? void 0 : options.onAccessDenied) return;
|
|
249
200
|
const authParams = (options === null || options === void 0 ? void 0 : options.authParams) ?? {};
|
|
250
201
|
redirectToSignIn({
|
|
251
202
|
returnUrl: options === null || options === void 0 ? void 0 : options.returnUrl,
|
|
@@ -258,11 +209,11 @@ const protectPage = (Component, options) => {
|
|
|
258
209
|
error
|
|
259
210
|
]);
|
|
260
211
|
if (error) return handlePageError(error, options);
|
|
261
|
-
if (!user && !isLoading && (options === null || options === void 0 ? void 0 : options.
|
|
212
|
+
if (!user && !isLoading && (options === null || options === void 0 ? void 0 : options.onAccessDenied)) return options.onAccessDenied();
|
|
262
213
|
if (user) {
|
|
263
214
|
if ((options === null || options === void 0 ? void 0 : options.groups) && !isUserInGroup(user, options.groups, options.groupsClaim ?? process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_GROUPS_CLAIM, options.matchAll)) {
|
|
264
|
-
const {
|
|
265
|
-
return
|
|
215
|
+
const { onGroupAccessDenied = () => /* @__PURE__ */ React.createElement("div", null, "Access Denied") } = options;
|
|
216
|
+
return onGroupAccessDenied(user);
|
|
266
217
|
}
|
|
267
218
|
return /* @__PURE__ */ React.createElement(Component, {
|
|
268
219
|
user,
|
|
@@ -274,5 +225,5 @@ const protectPage = (Component, options) => {
|
|
|
274
225
|
};
|
|
275
226
|
|
|
276
227
|
//#endregion
|
|
277
|
-
export { redirectToSignIn as n, useAuth as r,
|
|
278
|
-
//# sourceMappingURL=client-
|
|
228
|
+
export { redirectToSignIn as n, useAuth as r, protectClientPage as t };
|
|
229
|
+
//# sourceMappingURL=protect-client-page-BFlGkK8F.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protect-client-page-BFlGkK8F.mjs","names":[],"sources":["../src/client/use-auth.tsx","../src/client/protect-client-page.tsx"],"sourcesContent":["'use client';\n\nimport type { MonoCloudUser } from '@monocloud/auth-node-core';\nimport useSWR from 'swr';\n\n/**\n * Authentication State returned by `useAuth` hook.\n *\n * @category Types\n */\nexport interface AuthenticationState {\n /**\n * Flag indicating if the authentication state is still loading.\n */\n isLoading: boolean;\n /**\n * Flag indicating if the user is authenticated.\n */\n isAuthenticated: boolean;\n /**\n * Error encountered during authentication, if any.\n */\n error?: Error;\n /**\n * The authenticated user's information, if available.\n */\n user?: MonoCloudUser;\n /**\n * Function to refetch the authentication state.\n *\n */\n refetch: (refresh?: boolean) => void;\n}\n\nconst fetchUser = async (url: string): Promise<MonoCloudUser | undefined> => {\n const res = await fetch(url, { credentials: 'include' });\n\n if (res.status === 204) {\n return undefined;\n }\n\n if (res.ok) {\n return res.json();\n }\n\n throw new Error('Failed to fetch user');\n};\n\n/**\n *\n * `useAuth()` is a client-side hook that provides access to the current authentication state.\n *\n * It can only be used inside **Client Components**.\n *\n * @example Basic Usage\n * ```tsx title=\"Basic Usage\"\n * \"use client\";\n *\n * import { useAuth } from \"@monocloud/auth-nextjs/client\";\n *\n * export default function Home() {\n * const { user, isAuthenticated } = useAuth();\n *\n * if (!isAuthenticated) {\n * return <>Not signed in</>;\n * }\n *\n * return <>User Id: {user?.sub}</>;\n * }\n * ```\n *\n * @example Refetch user\n *\n * Calling `refetch(true)` forces a refresh of the user profile from the UserInfo endpoint.\n * Calling `refetch()` refreshes authentication state without forcing a UserInfo request.\n *\n * ```tsx title=\"Refetch User\"\n * \"use client\";\n *\n * import { useAuth } from \"@monocloud/auth-nextjs/client\";\n *\n * export default function Home() {\n * const { user, refetch } = useAuth();\n *\n * return (\n * <>\n * <pre>{JSON.stringify(user, null, 2)}</pre>\n * <button onClick={() => refetch(true)}>Refresh Profile</button>\n * </>\n * );\n * }\n * ```\n *\n * @returns\n *\n * @category Hooks\n */\nexport const useAuth = (): AuthenticationState => {\n const key =\n process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_USER_INFO_URL ??\n // eslint-disable-next-line no-underscore-dangle\n `${process.env.__NEXT_ROUTER_BASEPATH ?? ''}/api/auth/userinfo`;\n\n const { data, error, isLoading, mutate } = useSWR<MonoCloudUser | undefined>(\n key,\n fetchUser\n );\n\n const refetch = (refresh?: boolean): void => {\n const url = new URL(key, 'https://dummy');\n if (refresh) {\n url.searchParams.set('refresh', 'true');\n }\n\n void mutate(async () => await fetchUser(url.pathname + url.search), {\n revalidate: false,\n });\n };\n\n if (error) {\n return {\n user: undefined,\n isLoading: false,\n isAuthenticated: false,\n error: error as Error,\n refetch,\n };\n }\n\n if (data) {\n return {\n user: data,\n isLoading,\n isAuthenticated: !!data && Object.keys(data).length > 0,\n error: undefined,\n refetch,\n };\n }\n\n return {\n user: undefined,\n isLoading,\n isAuthenticated: false,\n error: undefined,\n /* v8 ignore next -- @preserve */\n refetch: (): void => {},\n };\n};\n","/* eslint-disable react/display-name */\n'use client';\n\nimport React, { ComponentType, useEffect } from 'react';\nimport type { MonoCloudUser } from '@monocloud/auth-node-core';\nimport { isUserInGroup } from '@monocloud/auth-node-core/utils';\nimport { useAuth } from './use-auth';\nimport { ExtraAuthParams, GroupOptions } from '../types';\nimport type { MonoCloudNextClient } from '../monocloud-next-client';\n\n/**\n * Options for configuring page protection.\n *\n * @category Types\n */\nexport interface ProtectClientPageOptions extends GroupOptions {\n /**\n * The URL where the user will be redirected to after sign in.\n */\n returnUrl?: string;\n\n /**\n * A custom react element to render when the user is not authenticated.\n */\n onAccessDenied?: () => React.ReactNode;\n\n /**\n * A custom react element to render when the user is authenticated but does not belong to the required groups.\n */\n onGroupAccessDenied?: (user: MonoCloudUser) => React.ReactNode;\n\n /**\n * Authorization parameters to be used during authentication.\n */\n authParams?: ExtraAuthParams;\n\n /**\n * Callback function to handle errors.\n * If not provided, errors will be thrown.\n *\n * @param error - The error object.\n * @returns JSX element to handle the error.\n */\n onError?: (error: Error) => React.ReactNode;\n}\n\nexport const redirectToSignIn = (\n options: { returnUrl?: string } & ExtraAuthParams\n): void => {\n const searchParams = new URLSearchParams(window.location.search);\n searchParams.set(\n 'return_url',\n options.returnUrl ?? window.location.toString()\n );\n\n if (options?.scopes) {\n searchParams.set('scope', options.scopes);\n }\n if (options?.resource) {\n searchParams.set('resource', options.resource);\n }\n\n if (options?.acrValues) {\n searchParams.set('acr_values', options.acrValues.join(' '));\n }\n\n if (options?.display) {\n searchParams.set('display', options.display);\n }\n\n if (options?.prompt) {\n searchParams.set('prompt', options.prompt);\n }\n\n if (options?.authenticatorHint) {\n searchParams.set('authenticator_hint', options.authenticatorHint);\n }\n\n if (options?.uiLocales) {\n searchParams.set('ui_locales', options.uiLocales);\n }\n\n if (options?.maxAge) {\n searchParams.set('max_age', options.maxAge.toString());\n }\n\n if (options?.loginHint) {\n searchParams.set('login_hint', options.loginHint);\n }\n\n window.location.assign(\n // eslint-disable-next-line no-underscore-dangle\n `${process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_SIGNIN_URL ?? `${process.env.__NEXT_ROUTER_BASEPATH ?? ''}/api/auth/signin`}?${searchParams.toString()}`\n );\n};\n\nconst handlePageError = (\n error: Error,\n options?: ProtectClientPageOptions\n): React.ReactNode => {\n /* v8 ignore else -- @preserve */\n if (options?.onError) {\n return options.onError(error);\n }\n\n /* v8 ignore next -- @preserve */\n throw error;\n};\n\n/**\n * `protectClientPage()` wraps a **client-rendered page component** and ensures that only authenticated users can access it.\n *\n * If the user is authenticated, the wrapped component receives a `user` prop.\n *\n * > This function runs on the client and controls rendering only.\n * > To enforce access before rendering (server-side), use the server {@link MonoCloudNextClient.protectPage | protectPage()} method on {@link MonoCloudNextClient}.\n *\n * @example Basic Usage\n *\n * ```tsx title=\"Basic Usage\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * });\n * ```\n *\n * @example With Options\n *\n * ```tsx title=\"With Options\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * },\n * {\n * returnUrl: \"/dashboard\",\n * authParams: { loginHint: \"user@example.com\" }\n * }\n * );\n * ```\n *\n * @example Custom access denied UI\n *\n * ```tsx title=\"Custom access denied UI\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Signed in as {user.email}</>;\n * },\n * {\n * onAccessDenied: () => <div>Please sign in to continue</div>\n * }\n * );\n * ```\n *\n * @example Group protection\n *\n * ```tsx title=\"Group protection\"\n * \"use client\";\n *\n * import { protectClientPage } from \"@monocloud/auth-nextjs/client\";\n *\n * export default protectClientPage(\n * function Home({ user }) {\n * return <>Welcome Admin {user.email}</>;\n * },\n * {\n * groups: [\"admin\"],\n * onGroupAccessDenied: (user) => <div>User {user.email} is not an admin</div>\n * }\n * );\n * ```\n *\n * @param Component - The page component to protect\n * @param options - Optional configuration\n * @returns A protected React component.\n *\n * @category Functions\n *\n */\nexport const protectClientPage = <P extends object>(\n Component: ComponentType<P & { user: MonoCloudUser }>,\n options?: ProtectClientPageOptions\n): React.FC<P> => {\n return props => {\n const { user, error, isLoading } = useAuth();\n\n useEffect(() => {\n if (!user && !isLoading && !error) {\n if (options?.onAccessDenied) {\n return;\n }\n\n const authParams = options?.authParams ?? {};\n redirectToSignIn({\n returnUrl: options?.returnUrl,\n ...authParams,\n });\n }\n }, [user, isLoading, error]);\n\n if (error) {\n return handlePageError(error, options);\n }\n\n if (!user && !isLoading && options?.onAccessDenied) {\n return options.onAccessDenied();\n }\n\n if (user) {\n if (\n options?.groups &&\n !isUserInGroup(\n user,\n options.groups,\n options.groupsClaim ??\n process.env.NEXT_PUBLIC_MONOCLOUD_AUTH_GROUPS_CLAIM,\n options.matchAll\n )\n ) {\n const {\n onGroupAccessDenied = (): React.ReactNode => <div>Access Denied</div>,\n } = options;\n return onGroupAccessDenied(user);\n }\n\n return <Component user={user} {...props} />;\n }\n\n return null;\n };\n};\n"],"mappings":"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"}
|