@imtbl/auth-nextjs 2.12.4-alpha.6 → 2.12.4-alpha.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 +66 -1
- package/dist/node/client/index.cjs +30 -4
- package/dist/node/client/index.js +30 -4
- package/dist/types/client/callback.d.ts +33 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -61,10 +61,12 @@ const config = {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
export default function Callback() {
|
|
64
|
-
return <CallbackPage config={config} />;
|
|
64
|
+
return <CallbackPage config={config} redirectTo="/dashboard" />;
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
See [CallbackPage Props](#callbackpage-props) for all available options.
|
|
69
|
+
|
|
68
70
|
### 4. Add Provider to Layout
|
|
69
71
|
|
|
70
72
|
```typescript
|
|
@@ -223,6 +225,69 @@ openssl rand -base64 32
|
|
|
223
225
|
| `useAccessToken()` | Hook returning `getAccessToken` function |
|
|
224
226
|
| `CallbackPage` | Pre-built callback page component for OAuth redirects |
|
|
225
227
|
|
|
228
|
+
#### CallbackPage Props
|
|
229
|
+
|
|
230
|
+
| Prop | Type | Default | Description |
|
|
231
|
+
| ------------------ | ----------------------------------------------------- | ------- | ------------------------------------------------------------------ |
|
|
232
|
+
| `config` | `ImmutableAuthConfig` | - | Required. Immutable auth configuration |
|
|
233
|
+
| `redirectTo` | `string \| ((user: ImmutableUser) => string \| void)` | `"/"` | Where to redirect after successful auth (supports dynamic routing) |
|
|
234
|
+
| `loadingComponent` | `React.ReactElement \| null` | `null` | Custom loading UI while processing authentication |
|
|
235
|
+
| `errorComponent` | `(error: string) => React.ReactElement \| null` | - | Custom error UI component |
|
|
236
|
+
| `onSuccess` | `(user: ImmutableUser) => void \| Promise<void>` | - | Callback fired after successful authentication |
|
|
237
|
+
| `onError` | `(error: string) => void` | - | Callback fired when authentication fails |
|
|
238
|
+
|
|
239
|
+
**Example with all props:**
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
// app/callback/page.tsx
|
|
243
|
+
"use client";
|
|
244
|
+
|
|
245
|
+
import { CallbackPage } from "@imtbl/auth-nextjs/client";
|
|
246
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
247
|
+
|
|
248
|
+
const config = {
|
|
249
|
+
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
250
|
+
redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default function Callback() {
|
|
254
|
+
return (
|
|
255
|
+
<CallbackPage
|
|
256
|
+
config={config}
|
|
257
|
+
// Dynamic redirect based on user
|
|
258
|
+
redirectTo={(user) => {
|
|
259
|
+
if (user.email?.endsWith("@admin.com")) return "/admin";
|
|
260
|
+
return "/dashboard";
|
|
261
|
+
}}
|
|
262
|
+
// Custom loading UI
|
|
263
|
+
loadingComponent={
|
|
264
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
265
|
+
<Spinner />
|
|
266
|
+
<span>Completing authentication...</span>
|
|
267
|
+
</div>
|
|
268
|
+
}
|
|
269
|
+
// Custom error UI
|
|
270
|
+
errorComponent={(error) => (
|
|
271
|
+
<div className="text-center p-8">
|
|
272
|
+
<h2 className="text-red-500">Authentication Error</h2>
|
|
273
|
+
<p>{error}</p>
|
|
274
|
+
<a href="/">Return Home</a>
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
// Success callback for analytics
|
|
278
|
+
onSuccess={async (user) => {
|
|
279
|
+
await analytics.track("login_success", { userId: user.sub });
|
|
280
|
+
}}
|
|
281
|
+
// Error callback for logging
|
|
282
|
+
onError={(error) => {
|
|
283
|
+
console.error("Auth failed:", error);
|
|
284
|
+
Sentry.captureMessage(error);
|
|
285
|
+
}}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
226
291
|
**`useImmutableAuth()` Return Value:**
|
|
227
292
|
|
|
228
293
|
| Property | Type | Description |
|
|
@@ -262,7 +262,9 @@ function CallbackPage({
|
|
|
262
262
|
config,
|
|
263
263
|
redirectTo = "/",
|
|
264
264
|
loadingComponent = null,
|
|
265
|
-
errorComponent
|
|
265
|
+
errorComponent,
|
|
266
|
+
onSuccess,
|
|
267
|
+
onError
|
|
266
268
|
}) {
|
|
267
269
|
const router = (0, import_navigation.useRouter)();
|
|
268
270
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
@@ -284,6 +286,14 @@ function CallbackPage({
|
|
|
284
286
|
if (!authUser) {
|
|
285
287
|
throw new Error("Authentication failed: no user data received from login callback");
|
|
286
288
|
}
|
|
289
|
+
const user = {
|
|
290
|
+
sub: authUser.profile.sub,
|
|
291
|
+
email: authUser.profile.email,
|
|
292
|
+
nickname: authUser.profile.nickname
|
|
293
|
+
};
|
|
294
|
+
if (onSuccess) {
|
|
295
|
+
await onSuccess(user);
|
|
296
|
+
}
|
|
287
297
|
window.close();
|
|
288
298
|
} else if (authUser) {
|
|
289
299
|
const tokenData = {
|
|
@@ -308,18 +318,34 @@ function CallbackPage({
|
|
|
308
318
|
if (!result?.ok) {
|
|
309
319
|
throw new Error("NextAuth sign-in failed: unknown error");
|
|
310
320
|
}
|
|
311
|
-
|
|
321
|
+
const user = {
|
|
322
|
+
sub: authUser.profile.sub,
|
|
323
|
+
email: authUser.profile.email,
|
|
324
|
+
nickname: authUser.profile.nickname
|
|
325
|
+
};
|
|
326
|
+
if (onSuccess) {
|
|
327
|
+
await onSuccess(user);
|
|
328
|
+
}
|
|
329
|
+
const resolvedRedirectTo = typeof redirectTo === "function" ? redirectTo(user) || "/" : redirectTo;
|
|
330
|
+
router.replace(resolvedRedirectTo);
|
|
312
331
|
} else {
|
|
313
332
|
throw new Error("Authentication failed: no user data received from login callback");
|
|
314
333
|
}
|
|
315
334
|
} catch (err) {
|
|
316
|
-
|
|
335
|
+
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
336
|
+
if (onError) {
|
|
337
|
+
onError(errorMessage);
|
|
338
|
+
}
|
|
339
|
+
setError(errorMessage);
|
|
317
340
|
}
|
|
318
341
|
};
|
|
319
342
|
const handleOAuthError = () => {
|
|
320
343
|
const errorCode = searchParams.get("error");
|
|
321
344
|
const errorDescription = searchParams.get("error_description");
|
|
322
345
|
const errorMessage = errorDescription || errorCode || "Authentication failed";
|
|
346
|
+
if (onError) {
|
|
347
|
+
onError(errorMessage);
|
|
348
|
+
}
|
|
323
349
|
setError(errorMessage);
|
|
324
350
|
};
|
|
325
351
|
if (searchParams.get("error")) {
|
|
@@ -330,7 +356,7 @@ function CallbackPage({
|
|
|
330
356
|
callbackProcessedRef.current = true;
|
|
331
357
|
handleCallback();
|
|
332
358
|
}
|
|
333
|
-
}, [searchParams, router, config, redirectTo]);
|
|
359
|
+
}, [searchParams, router, config, redirectTo, onSuccess, onError]);
|
|
334
360
|
if (error) {
|
|
335
361
|
if (errorComponent) {
|
|
336
362
|
return errorComponent(error);
|
|
@@ -248,7 +248,9 @@ function CallbackPage({
|
|
|
248
248
|
config,
|
|
249
249
|
redirectTo = "/",
|
|
250
250
|
loadingComponent = null,
|
|
251
|
-
errorComponent
|
|
251
|
+
errorComponent,
|
|
252
|
+
onSuccess,
|
|
253
|
+
onError
|
|
252
254
|
}) {
|
|
253
255
|
const router = useRouter();
|
|
254
256
|
const searchParams = useSearchParams();
|
|
@@ -270,6 +272,14 @@ function CallbackPage({
|
|
|
270
272
|
if (!authUser) {
|
|
271
273
|
throw new Error("Authentication failed: no user data received from login callback");
|
|
272
274
|
}
|
|
275
|
+
const user = {
|
|
276
|
+
sub: authUser.profile.sub,
|
|
277
|
+
email: authUser.profile.email,
|
|
278
|
+
nickname: authUser.profile.nickname
|
|
279
|
+
};
|
|
280
|
+
if (onSuccess) {
|
|
281
|
+
await onSuccess(user);
|
|
282
|
+
}
|
|
273
283
|
window.close();
|
|
274
284
|
} else if (authUser) {
|
|
275
285
|
const tokenData = {
|
|
@@ -294,18 +304,34 @@ function CallbackPage({
|
|
|
294
304
|
if (!result?.ok) {
|
|
295
305
|
throw new Error("NextAuth sign-in failed: unknown error");
|
|
296
306
|
}
|
|
297
|
-
|
|
307
|
+
const user = {
|
|
308
|
+
sub: authUser.profile.sub,
|
|
309
|
+
email: authUser.profile.email,
|
|
310
|
+
nickname: authUser.profile.nickname
|
|
311
|
+
};
|
|
312
|
+
if (onSuccess) {
|
|
313
|
+
await onSuccess(user);
|
|
314
|
+
}
|
|
315
|
+
const resolvedRedirectTo = typeof redirectTo === "function" ? redirectTo(user) || "/" : redirectTo;
|
|
316
|
+
router.replace(resolvedRedirectTo);
|
|
298
317
|
} else {
|
|
299
318
|
throw new Error("Authentication failed: no user data received from login callback");
|
|
300
319
|
}
|
|
301
320
|
} catch (err) {
|
|
302
|
-
|
|
321
|
+
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
322
|
+
if (onError) {
|
|
323
|
+
onError(errorMessage);
|
|
324
|
+
}
|
|
325
|
+
setError(errorMessage);
|
|
303
326
|
}
|
|
304
327
|
};
|
|
305
328
|
const handleOAuthError = () => {
|
|
306
329
|
const errorCode = searchParams.get("error");
|
|
307
330
|
const errorDescription = searchParams.get("error_description");
|
|
308
331
|
const errorMessage = errorDescription || errorCode || "Authentication failed";
|
|
332
|
+
if (onError) {
|
|
333
|
+
onError(errorMessage);
|
|
334
|
+
}
|
|
309
335
|
setError(errorMessage);
|
|
310
336
|
};
|
|
311
337
|
if (searchParams.get("error")) {
|
|
@@ -316,7 +342,7 @@ function CallbackPage({
|
|
|
316
342
|
callbackProcessedRef.current = true;
|
|
317
343
|
handleCallback();
|
|
318
344
|
}
|
|
319
|
-
}, [searchParams, router, config, redirectTo]);
|
|
345
|
+
}, [searchParams, router, config, redirectTo, onSuccess, onError]);
|
|
320
346
|
if (error) {
|
|
321
347
|
if (errorComponent) {
|
|
322
348
|
return errorComponent(error);
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ImmutableAuthConfig } from '../types';
|
|
2
|
+
import type { ImmutableAuthConfig, ImmutableUser } from '../types';
|
|
3
3
|
export interface CallbackPageProps {
|
|
4
4
|
/**
|
|
5
5
|
* Immutable auth configuration
|
|
6
6
|
*/
|
|
7
7
|
config: ImmutableAuthConfig;
|
|
8
8
|
/**
|
|
9
|
-
* URL to redirect to after successful authentication (when not in popup)
|
|
9
|
+
* URL to redirect to after successful authentication (when not in popup).
|
|
10
|
+
* Can be a string or a function that receives the authenticated user.
|
|
11
|
+
* If a function returns void/undefined, defaults to "/".
|
|
12
|
+
* @default "/"
|
|
13
|
+
*
|
|
14
|
+
* @example Static redirect
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <CallbackPage config={config} redirectTo="/dashboard" />
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Dynamic redirect based on user
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <CallbackPage
|
|
22
|
+
* config={config}
|
|
23
|
+
* redirectTo={(user) => user.email?.endsWith('@admin.com') ? '/admin' : '/dashboard'}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
10
26
|
*/
|
|
11
|
-
redirectTo?: string;
|
|
27
|
+
redirectTo?: string | ((user: ImmutableUser) => string | void);
|
|
12
28
|
/**
|
|
13
29
|
* Custom loading component
|
|
14
30
|
*/
|
|
@@ -17,6 +33,19 @@ export interface CallbackPageProps {
|
|
|
17
33
|
* Custom error component
|
|
18
34
|
*/
|
|
19
35
|
errorComponent?: (error: string) => React.ReactElement | null;
|
|
36
|
+
/**
|
|
37
|
+
* Callback fired after successful authentication.
|
|
38
|
+
* Receives the authenticated user as a parameter.
|
|
39
|
+
* Called before redirect (non-popup) or before window.close (popup).
|
|
40
|
+
* If this callback returns a Promise, it will be awaited before proceeding.
|
|
41
|
+
*/
|
|
42
|
+
onSuccess?: (user: ImmutableUser) => void | Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Callback fired when authentication fails.
|
|
45
|
+
* Receives the error message as a parameter.
|
|
46
|
+
* Called before the error UI is displayed.
|
|
47
|
+
*/
|
|
48
|
+
onError?: (error: string) => void;
|
|
20
49
|
}
|
|
21
50
|
/**
|
|
22
51
|
* Callback page component for handling OAuth redirects (App Router version).
|
|
@@ -39,4 +68,4 @@ export interface CallbackPageProps {
|
|
|
39
68
|
* }
|
|
40
69
|
* ```
|
|
41
70
|
*/
|
|
42
|
-
export declare function CallbackPage({ config, redirectTo, loadingComponent, errorComponent, }: CallbackPageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
71
|
+
export declare function CallbackPage({ config, redirectTo, loadingComponent, errorComponent, onSuccess, onError, }: CallbackPageProps): import("react/jsx-runtime").JSX.Element | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imtbl/auth-nextjs",
|
|
3
|
-
"version": "2.12.4-alpha.
|
|
3
|
+
"version": "2.12.4-alpha.7",
|
|
4
4
|
"description": "Next.js App Router authentication integration for Immutable SDK using Auth.js v5",
|
|
5
5
|
"author": "Immutable",
|
|
6
6
|
"bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dist"
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@imtbl/auth": "2.12.4-alpha.
|
|
54
|
+
"@imtbl/auth": "2.12.4-alpha.7"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"next": "14.2.25",
|