@monocloud/auth-nextjs 0.1.6 → 0.1.8

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.
@@ -11,32 +11,33 @@ const fetchUser = async (url) => {
11
11
  };
12
12
  /**
13
13
  *
14
- * Hook for getting the user's profile on client components
14
+ * `useAuth()` is a client-side hook that provides access to the current authentication state.
15
15
  *
16
- * @returns Authentication State
16
+ * It can only be used inside **Client Components**.
17
17
  *
18
- * @example App Router
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 App Router - Refetch user from Userinfo endpoint
35
+ * @example Refetch user
33
36
  *
34
- * Calling `refetch(true)` will force refresh the user's profile from the userinfo endpoint.
35
- * If you do not intent to refersh from your tenants userinfo endpoint, use just `refetch()`
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
- * **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**
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
- * @example Pages Router
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,54 +112,53 @@ const handlePageError = (error, options) => {
142
112
  throw error;
143
113
  };
144
114
  /**
145
- * Function to protect a client rendered page component.
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
- * @param Component - The component to protect.
151
- * @param options - The options.
117
+ * If the user is authenticated, the wrapped component receives a `user` prop.
152
118
  *
153
- * @returns Protected client rendered page component.
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 App Router
122
+ * @example Basic Usage
156
123
  *
157
- * ```tsx
124
+ * ```tsx title="Basic Usage"
158
125
  * "use client";
159
126
  *
160
- * import { protectPage } from "@monocloud/auth-nextjs/client";
127
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
161
128
  *
162
- * export default protectPage(function Home() {
163
- * return <>You are signed in</>;
129
+ * export default protectClientPage(function Home({ user }) {
130
+ * return <>Signed in as {user.email}</>;
164
131
  * });
165
132
  * ```
166
133
  *
167
- * @example App Router with options
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 { protectPage } from "@monocloud/auth-nextjs/client";
139
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
175
140
  *
176
- * export default protectPage(
177
- * function Home() {
178
- * return <>You are signed in</>;
141
+ * export default protectClientPage(
142
+ * function Home({ user }) {
143
+ * return <>Signed in as {user.email}</>;
179
144
  * },
180
- * { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
145
+ * {
146
+ * returnUrl: "/dashboard",
147
+ * authParams: { loginHint: "user@example.com" }
148
+ * }
181
149
  * );
182
150
  * ```
183
- * @example Fallback with onAccessDenied
184
151
  *
185
- * ```tsx
152
+ * @example Custom access denied UI
153
+ *
154
+ * ```tsx title="Custom access denied UI"
186
155
  * "use client";
187
156
  *
188
- * import { protectPage } from "@monocloud/auth-nextjs/client";
157
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
189
158
  *
190
- * export default protectPage(
191
- * function Home() {
192
- * return <>You are signed in</>;
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>
@@ -197,16 +166,16 @@ const handlePageError = (error, options) => {
197
166
  * );
198
167
  * ```
199
168
  *
200
- * @example Group Protection with onGroupAccessDenied
169
+ * @example Group protection
201
170
  *
202
- * ```tsx
171
+ * ```tsx title="Group protection"
203
172
  * "use client";
204
173
  *
205
- * import { protectPage } from "@monocloud/auth-nextjs/client";
174
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
206
175
  *
207
- * export default protectPage(
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"],
@@ -215,32 +184,15 @@ const handlePageError = (error, options) => {
215
184
  * );
216
185
  * ```
217
186
  *
218
- * @example Pages Router
219
- *
220
- * ```tsx
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
+ * @typeParam P - Props of the protected component (excluding `user`).
189
+ * @param options - Optional configuration
190
+ * @returns A protected React component.
231
191
  *
232
- * ```tsx
233
- * import { protectPage } from "@monocloud/auth-nextjs/client";
192
+ * @category Functions
234
193
  *
235
- * export default protectPage(
236
- * function Home() {
237
- * return <>You are signed in</>;
238
- * },
239
- * { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
240
- * );
241
- * ```
242
194
  */
243
- const protectPage = (Component, options) => {
195
+ const protectClientPage = (Component, options) => {
244
196
  return (props) => {
245
197
  const { user, error, isLoading } = useAuth();
246
198
  useEffect(() => {
@@ -274,5 +226,5 @@ const protectPage = (Component, options) => {
274
226
  };
275
227
 
276
228
  //#endregion
277
- export { redirectToSignIn as n, useAuth as r, protectPage as t };
278
- //# sourceMappingURL=protect-K9srvUkq.mjs.map
229
+ export { redirectToSignIn as n, useAuth as r, protectClientPage as t };
230
+ //# sourceMappingURL=protect-client-page-BFVskb3X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect-client-page-BFVskb3X.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 * @typeParam P - Props of the protected component (excluding `user`).\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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"}
@@ -14,32 +14,33 @@ const fetchUser = async (url) => {
14
14
  };
15
15
  /**
16
16
  *
17
- * Hook for getting the user's profile on client components
17
+ * `useAuth()` is a client-side hook that provides access to the current authentication state.
18
18
  *
19
- * @returns Authentication State
19
+ * It can only be used inside **Client Components**.
20
20
  *
21
- * @example App Router
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 App Router - Refetch user from Userinfo endpoint
38
+ * @example Refetch user
36
39
  *
37
- * Calling `refetch(true)` will force refresh the user's profile from the userinfo endpoint.
38
- * If you do not intent to refersh from your tenants userinfo endpoint, use just `refetch()`
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
- * **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**
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
- * @example Pages Router
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,54 +115,53 @@ const handlePageError = (error, options) => {
145
115
  throw error;
146
116
  };
147
117
  /**
148
- * Function to protect a client rendered page component.
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
- * @param Component - The component to protect.
154
- * @param options - The options.
120
+ * If the user is authenticated, the wrapped component receives a `user` prop.
155
121
  *
156
- * @returns Protected client rendered page component.
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 App Router
125
+ * @example Basic Usage
159
126
  *
160
- * ```tsx
127
+ * ```tsx title="Basic Usage"
161
128
  * "use client";
162
129
  *
163
- * import { protectPage } from "@monocloud/auth-nextjs/client";
130
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
164
131
  *
165
- * export default protectPage(function Home() {
166
- * return <>You are signed in</>;
132
+ * export default protectClientPage(function Home({ user }) {
133
+ * return <>Signed in as {user.email}</>;
167
134
  * });
168
135
  * ```
169
136
  *
170
- * @example App Router with options
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 { protectPage } from "@monocloud/auth-nextjs/client";
142
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
178
143
  *
179
- * export default protectPage(
180
- * function Home() {
181
- * return <>You are signed in</>;
144
+ * export default protectClientPage(
145
+ * function Home({ user }) {
146
+ * return <>Signed in as {user.email}</>;
182
147
  * },
183
- * { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
148
+ * {
149
+ * returnUrl: "/dashboard",
150
+ * authParams: { loginHint: "user@example.com" }
151
+ * }
184
152
  * );
185
153
  * ```
186
- * @example Fallback with onAccessDenied
187
154
  *
188
- * ```tsx
155
+ * @example Custom access denied UI
156
+ *
157
+ * ```tsx title="Custom access denied UI"
189
158
  * "use client";
190
159
  *
191
- * import { protectPage } from "@monocloud/auth-nextjs/client";
160
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
192
161
  *
193
- * export default protectPage(
194
- * function Home() {
195
- * return <>You are signed in</>;
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>
@@ -200,16 +169,16 @@ const handlePageError = (error, options) => {
200
169
  * );
201
170
  * ```
202
171
  *
203
- * @example Group Protection with onGroupAccessDenied
172
+ * @example Group protection
204
173
  *
205
- * ```tsx
174
+ * ```tsx title="Group protection"
206
175
  * "use client";
207
176
  *
208
- * import { protectPage } from "@monocloud/auth-nextjs/client";
177
+ * import { protectClientPage } from "@monocloud/auth-nextjs/client";
209
178
  *
210
- * export default protectPage(
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"],
@@ -218,32 +187,15 @@ const handlePageError = (error, options) => {
218
187
  * );
219
188
  * ```
220
189
  *
221
- * @example Pages Router
222
- *
223
- * ```tsx
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
+ * @typeParam P - Props of the protected component (excluding `user`).
192
+ * @param options - Optional configuration
193
+ * @returns A protected React component.
234
194
  *
235
- * ```tsx
236
- * import { protectPage } from "@monocloud/auth-nextjs/client";
195
+ * @category Functions
237
196
  *
238
- * export default protectPage(
239
- * function Home() {
240
- * return <>You are signed in</>;
241
- * },
242
- * { returnUrl: "/dashboard", authParams: { loginHint: "username" } }
243
- * );
244
- * ```
245
197
  */
246
- const protectPage = (Component, options) => {
198
+ const protectClientPage = (Component, options) => {
247
199
  return (props) => {
248
200
  const { user, error, isLoading } = useAuth();
249
201
  (0, react.useEffect)(() => {
@@ -277,10 +229,10 @@ const protectPage = (Component, options) => {
277
229
  };
278
230
 
279
231
  //#endregion
280
- Object.defineProperty(exports, 'protectPage', {
232
+ Object.defineProperty(exports, 'protectClientPage', {
281
233
  enumerable: true,
282
234
  get: function () {
283
- return protectPage;
235
+ return protectClientPage;
284
236
  }
285
237
  });
286
238
  Object.defineProperty(exports, 'redirectToSignIn', {
@@ -295,4 +247,4 @@ Object.defineProperty(exports, 'useAuth', {
295
247
  return useAuth;
296
248
  }
297
249
  });
298
- //# sourceMappingURL=protect-BCIji2i7.cjs.map
250
+ //# sourceMappingURL=protect-client-page-BdsnH8gs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect-client-page-BdsnH8gs.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 * @typeParam P - Props of the protected component (excluding `user`).\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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"}