@imtbl/auth-next-server 2.12.5-alpha.13

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.
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @imtbl/auth-next-server
3
+ *
4
+ * Server-side utilities for Immutable Auth.js v5 integration with Next.js.
5
+ * This package has NO dependency on @imtbl/auth and is safe to use in
6
+ * Next.js middleware and Edge Runtime environments.
7
+ *
8
+ * For client-side components (provider, hooks, callback), use @imtbl/auth-next-client.
9
+ */
10
+ import NextAuthImport from 'next-auth';
11
+ import type { NextAuthConfig, Session } from 'next-auth';
12
+ import { type NextRequest, NextResponse } from 'next/server';
13
+ import type { ImmutableAuthConfig } from './types';
14
+ declare const NextAuth: typeof NextAuthImport;
15
+ /**
16
+ * Auth.js v5 config options that can be overridden.
17
+ * Excludes 'providers' as that's managed internally.
18
+ */
19
+ export type ImmutableAuthOverrides = Omit<NextAuthConfig, 'providers'>;
20
+ /**
21
+ * Return type of createImmutableAuth - the NextAuth instance with handlers
22
+ */
23
+ export type ImmutableAuthResult = ReturnType<typeof NextAuth>;
24
+ /**
25
+ * Create an Auth.js v5 instance with Immutable authentication
26
+ *
27
+ * @param config - Immutable auth configuration
28
+ * @param overrides - Optional Auth.js options to override defaults
29
+ * @returns NextAuth instance with { handlers, auth, signIn, signOut }
30
+ *
31
+ * @remarks
32
+ * Callback composition: The `jwt` and `session` callbacks are composed rather than
33
+ * replaced. Internal callbacks run first (handling token storage and refresh), then
34
+ * your custom callbacks receive the result. Other callbacks (`signIn`, `redirect`)
35
+ * are replaced entirely if provided.
36
+ *
37
+ * @example Basic usage (App Router)
38
+ * ```typescript
39
+ * // lib/auth.ts
40
+ * import { createImmutableAuth } from "@imtbl/auth-next-server";
41
+ *
42
+ * export const { handlers, auth, signIn, signOut } = createImmutableAuth({
43
+ * clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
44
+ * redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
45
+ * });
46
+ *
47
+ * // app/api/auth/[...nextauth]/route.ts
48
+ * import { handlers } from "@/lib/auth";
49
+ * export const { GET, POST } = handlers;
50
+ * ```
51
+ */
52
+ export declare function createImmutableAuth(config: ImmutableAuthConfig, overrides?: ImmutableAuthOverrides): ImmutableAuthResult;
53
+ export { createAuthConfig, createAuthOptions } from './config';
54
+ export type { ImmutableAuthConfig, ImmutableTokenData, UserInfoResponse, ZkEvmUser, ImmutableUser, } from './types';
55
+ /**
56
+ * Result from getValidSession indicating auth state
57
+ */
58
+ export type ValidSessionResult = {
59
+ status: 'authenticated';
60
+ session: Session;
61
+ } | {
62
+ status: 'token_expired';
63
+ session: Session;
64
+ } | {
65
+ status: 'unauthenticated';
66
+ session: null;
67
+ } | {
68
+ status: 'error';
69
+ session: Session;
70
+ error: string;
71
+ };
72
+ /**
73
+ * Auth props to pass to components - enables automatic SSR/CSR switching.
74
+ * When token is valid, session contains accessToken for immediate use.
75
+ * When token is expired, ssr is false and component should fetch client-side.
76
+ */
77
+ export interface AuthProps {
78
+ /** Session with valid tokens, or null if token expired/unauthenticated */
79
+ session: Session | null;
80
+ /** If true, SSR data fetching occurred with valid token */
81
+ ssr: boolean;
82
+ /** Auth error that requires user action (not TokenExpired) */
83
+ authError?: string;
84
+ }
85
+ /**
86
+ * Auth props with pre-fetched data for SSR hydration.
87
+ * Extends AuthProps with optional data that was fetched server-side.
88
+ */
89
+ export interface AuthPropsWithData<T> extends AuthProps {
90
+ /** Pre-fetched data from server (null if SSR was skipped or fetch failed) */
91
+ data: T | null;
92
+ /** Error message if server-side fetch failed */
93
+ fetchError?: string;
94
+ }
95
+ /**
96
+ * Auth props without the authError field.
97
+ * Used when auth error handling is automatic via onAuthError callback.
98
+ */
99
+ export interface ProtectedAuthProps {
100
+ /** Session with valid tokens, or null if token expired/unauthenticated */
101
+ session: Session | null;
102
+ /** If true, SSR data fetching occurred with valid token */
103
+ ssr: boolean;
104
+ }
105
+ /**
106
+ * Protected auth props with pre-fetched data.
107
+ * Used when auth error handling is automatic via onAuthError callback.
108
+ */
109
+ export interface ProtectedAuthPropsWithData<T> extends ProtectedAuthProps {
110
+ /** Pre-fetched data from server (null if SSR was skipped or fetch failed) */
111
+ data: T | null;
112
+ /** Error message if server-side fetch failed */
113
+ fetchError?: string;
114
+ }
115
+ /**
116
+ * Type for the auth function returned by createImmutableAuth
117
+ */
118
+ export type AuthFunction = () => Promise<Session | null>;
119
+ /**
120
+ * Get auth props for passing to Client Components (without data fetching).
121
+ * Use this when you want to handle data fetching separately or client-side only.
122
+ *
123
+ * For SSR data fetching, use `getAuthenticatedData` instead.
124
+ *
125
+ * @param auth - The auth function from createImmutableAuth
126
+ * @returns AuthProps with session and ssr flag
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const authProps = await getAuthProps(auth);
131
+ * if (authProps.authError) redirect("/login");
132
+ * return <MyComponent {...authProps} />;
133
+ * ```
134
+ */
135
+ export declare function getAuthProps(auth: AuthFunction): Promise<AuthProps>;
136
+ /**
137
+ * Fetch authenticated data on the server with automatic SSR/CSR switching.
138
+ *
139
+ * This is the recommended pattern for Server Components that need authenticated data:
140
+ * - When token is valid: Fetches data server-side, returns with `ssr: true`
141
+ * - When token is expired: Skips fetch, returns `ssr: false` for client-side handling
142
+ *
143
+ * @param auth - The auth function from createImmutableAuth
144
+ * @param fetcher - Async function that receives access token and returns data
145
+ * @returns AuthPropsWithData containing session, ssr flag, and pre-fetched data
146
+ */
147
+ export declare function getAuthenticatedData<T>(auth: AuthFunction, fetcher: (accessToken: string) => Promise<T>): Promise<AuthPropsWithData<T>>;
148
+ /**
149
+ * Get session with detailed status for Server Components.
150
+ * Use this when you need fine-grained control over different auth states.
151
+ *
152
+ * @param auth - The auth function from createImmutableAuth
153
+ * @returns Object with status and session
154
+ */
155
+ export declare function getValidSession(auth: AuthFunction): Promise<ValidSessionResult>;
156
+ /**
157
+ * Auth error handler signature.
158
+ * The handler should either redirect (using Next.js redirect()) or throw an error.
159
+ * It must never return normally - hence the `never` return type.
160
+ *
161
+ * @param error - The auth error (e.g., "RefreshTokenError")
162
+ */
163
+ export type AuthErrorHandler = (error: string) => never;
164
+ /**
165
+ * Create a protected data fetcher with automatic auth error handling.
166
+ *
167
+ * This eliminates the need to check `authError` on every page. Define the error
168
+ * handling once, and all pages using this fetcher will automatically redirect
169
+ * on auth errors.
170
+ *
171
+ * @param auth - The auth function from createImmutableAuth
172
+ * @param onAuthError - Handler called when there's an auth error (should redirect or throw)
173
+ * @returns A function to fetch protected data without needing authError checks
174
+ */
175
+ export declare function createProtectedDataFetcher(auth: AuthFunction, onAuthError: AuthErrorHandler): <T>(fetcher: (accessToken: string) => Promise<T>) => Promise<ProtectedAuthPropsWithData<T>>;
176
+ /**
177
+ * Create auth props getter with automatic auth error handling.
178
+ *
179
+ * Similar to createProtectedDataFetcher but for cases where you don't need
180
+ * server-side data fetching.
181
+ *
182
+ * @param auth - The auth function from createImmutableAuth
183
+ * @param onAuthError - Handler called when there's an auth error (should redirect or throw)
184
+ * @returns A function to get auth props without needing authError checks
185
+ */
186
+ export declare function createProtectedAuthProps(auth: AuthFunction, onAuthError: AuthErrorHandler): () => Promise<ProtectedAuthProps>;
187
+ /**
188
+ * Result of createProtectedFetchers
189
+ */
190
+ export interface ProtectedFetchers {
191
+ /**
192
+ * Get auth props with automatic auth error handling.
193
+ * No data fetching - use when you only need session/auth state.
194
+ */
195
+ getAuthProps: () => Promise<ProtectedAuthProps>;
196
+ /**
197
+ * Fetch authenticated data with automatic auth error handling.
198
+ * Use for SSR data fetching with automatic fallback.
199
+ */
200
+ getData: <T>(fetcher: (accessToken: string) => Promise<T>) => Promise<ProtectedAuthPropsWithData<T>>;
201
+ }
202
+ /**
203
+ * Create protected fetchers with centralized auth error handling.
204
+ *
205
+ * This is the recommended way to set up auth error handling once and use it
206
+ * across all protected pages. Define your error handler once, then use the
207
+ * returned functions without needing to check authError on each page.
208
+ *
209
+ * @param auth - The auth function from createImmutableAuth
210
+ * @param onAuthError - Handler called when there's an auth error (should redirect or throw)
211
+ * @returns Object with getAuthProps and getData functions
212
+ */
213
+ export declare function createProtectedFetchers(auth: AuthFunction, onAuthError: AuthErrorHandler): ProtectedFetchers;
214
+ /**
215
+ * Options for withServerAuth
216
+ */
217
+ export interface WithServerAuthOptions<TFallback> {
218
+ /**
219
+ * Content to render when token is expired.
220
+ * This should typically be a Client Component that will refresh tokens and fetch data.
221
+ * If not provided, the serverRender function will still be called with the expired session.
222
+ */
223
+ onTokenExpired?: TFallback | (() => TFallback);
224
+ /**
225
+ * Content to render when user is not authenticated at all.
226
+ * If not provided, throws an error.
227
+ */
228
+ onUnauthenticated?: TFallback | (() => TFallback);
229
+ /**
230
+ * Content to render when there's an auth error (e.g., refresh token invalid).
231
+ * If not provided, throws an error.
232
+ */
233
+ onError?: TFallback | ((error: string) => TFallback);
234
+ }
235
+ /**
236
+ * Helper for Server Components that need authenticated data.
237
+ * Automatically handles token expiration by rendering a client fallback.
238
+ *
239
+ * @param auth - The auth function from createImmutableAuth
240
+ * @param serverRender - Async function that receives valid session and returns JSX
241
+ * @param options - Fallback options for different auth states
242
+ * @returns The rendered content based on auth state
243
+ */
244
+ export declare function withServerAuth<TResult, TFallback = TResult>(auth: AuthFunction, serverRender: (session: Session) => Promise<TResult>, options?: WithServerAuthOptions<TFallback>): Promise<TResult | TFallback>;
245
+ /**
246
+ * Options for createAuthMiddleware
247
+ */
248
+ export interface AuthMiddlewareOptions {
249
+ /**
250
+ * URL to redirect to when not authenticated
251
+ * @default "/login"
252
+ */
253
+ loginUrl?: string;
254
+ /**
255
+ * Paths that should be protected (regex patterns)
256
+ * If not provided, middleware should be configured via Next.js matcher
257
+ */
258
+ protectedPaths?: (string | RegExp)[];
259
+ /**
260
+ * Paths that should be excluded from protection (regex patterns)
261
+ * Takes precedence over protectedPaths
262
+ */
263
+ publicPaths?: (string | RegExp)[];
264
+ }
265
+ /**
266
+ * Create a Next.js middleware for protecting routes with Immutable authentication.
267
+ *
268
+ * This is the App Router replacement for `withPageAuthRequired`.
269
+ *
270
+ * @param auth - The auth function from createImmutableAuth
271
+ * @param options - Middleware options
272
+ * @returns A Next.js middleware function
273
+ *
274
+ * @example Basic usage with Next.js middleware:
275
+ * ```typescript
276
+ * // middleware.ts
277
+ * import { createAuthMiddleware } from "@imtbl/auth-next-server";
278
+ * import { auth } from "@/lib/auth";
279
+ *
280
+ * export default createAuthMiddleware(auth, {
281
+ * loginUrl: "/login",
282
+ * });
283
+ *
284
+ * export const config = {
285
+ * matcher: ["/dashboard/:path*", "/profile/:path*"],
286
+ * };
287
+ * ```
288
+ */
289
+ export declare function createAuthMiddleware(auth: AuthFunction, options?: AuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
290
+ /**
291
+ * Higher-order function to protect a Server Action or Route Handler.
292
+ *
293
+ * The returned function forwards all arguments from Next.js to your handler,
294
+ * allowing access to the request, context, form data, or any other arguments.
295
+ *
296
+ * @param auth - The auth function from createImmutableAuth
297
+ * @param handler - The handler function to protect. Receives session as first arg,
298
+ * followed by any arguments passed by Next.js (request, context, etc.)
299
+ * @returns A protected handler that checks authentication before executing
300
+ */
301
+ export declare function withAuth<TArgs extends unknown[], TReturn>(auth: AuthFunction, handler: (session: Session, ...args: TArgs) => Promise<TReturn>): (...args: TArgs) => Promise<TReturn>;
@@ -0,0 +1,390 @@
1
+ // src/index.ts
2
+ import NextAuthImport from "next-auth";
3
+ import { NextResponse } from "next/server";
4
+
5
+ // src/config.ts
6
+ import CredentialsImport from "next-auth/providers/credentials";
7
+
8
+ // src/constants.ts
9
+ var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
10
+ var IMMUTABLE_PROVIDER_ID = "immutable";
11
+ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
12
+ var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
13
+ var TOKEN_EXPIRY_BUFFER_SECONDS = 60;
14
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
15
+
16
+ // src/refresh.ts
17
+ function isTokenExpired(accessTokenExpires, bufferSeconds = TOKEN_EXPIRY_BUFFER_SECONDS) {
18
+ if (typeof accessTokenExpires !== "number" || Number.isNaN(accessTokenExpires)) {
19
+ return true;
20
+ }
21
+ return Date.now() >= accessTokenExpires - bufferSeconds * 1e3;
22
+ }
23
+
24
+ // src/config.ts
25
+ var Credentials = CredentialsImport.default || CredentialsImport;
26
+ async function validateTokens(accessToken, authDomain) {
27
+ try {
28
+ const response = await fetch(`${authDomain}/userinfo`, {
29
+ method: "GET",
30
+ headers: {
31
+ Authorization: `Bearer ${accessToken}`
32
+ }
33
+ });
34
+ if (!response.ok) {
35
+ console.error("[auth-next-server] Token validation failed:", response.status, response.statusText);
36
+ return null;
37
+ }
38
+ return await response.json();
39
+ } catch (error) {
40
+ console.error("[auth-next-server] Token validation error:", error);
41
+ return null;
42
+ }
43
+ }
44
+ function createAuthConfig(config) {
45
+ const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
46
+ return {
47
+ providers: [
48
+ Credentials({
49
+ id: IMMUTABLE_PROVIDER_ID,
50
+ name: "Immutable",
51
+ credentials: {
52
+ tokens: { label: "Tokens", type: "text" }
53
+ },
54
+ async authorize(credentials) {
55
+ if (!credentials?.tokens || typeof credentials.tokens !== "string") {
56
+ return null;
57
+ }
58
+ let tokenData;
59
+ try {
60
+ tokenData = JSON.parse(credentials.tokens);
61
+ } catch (error) {
62
+ console.error("[auth-next-server] Failed to parse token data:", error);
63
+ return null;
64
+ }
65
+ if (!tokenData.accessToken || typeof tokenData.accessToken !== "string" || !tokenData.profile || typeof tokenData.profile !== "object" || !tokenData.profile.sub || typeof tokenData.profile.sub !== "string" || typeof tokenData.accessTokenExpires !== "number" || Number.isNaN(tokenData.accessTokenExpires)) {
66
+ console.error("[auth-next-server] Invalid token data structure - missing required fields");
67
+ return null;
68
+ }
69
+ const userInfo = await validateTokens(tokenData.accessToken, authDomain);
70
+ if (!userInfo) {
71
+ console.error("[auth-next-server] Token validation failed - rejecting authentication");
72
+ return null;
73
+ }
74
+ if (userInfo.sub !== tokenData.profile.sub) {
75
+ console.error(
76
+ "[auth-next-server] User ID mismatch - userinfo sub:",
77
+ userInfo.sub,
78
+ "provided sub:",
79
+ tokenData.profile.sub
80
+ );
81
+ return null;
82
+ }
83
+ return {
84
+ id: userInfo.sub,
85
+ sub: userInfo.sub,
86
+ email: userInfo.email ?? tokenData.profile.email,
87
+ nickname: userInfo.nickname ?? tokenData.profile.nickname,
88
+ accessToken: tokenData.accessToken,
89
+ refreshToken: tokenData.refreshToken,
90
+ idToken: tokenData.idToken,
91
+ accessTokenExpires: tokenData.accessTokenExpires,
92
+ zkEvm: tokenData.zkEvm
93
+ };
94
+ }
95
+ })
96
+ ],
97
+ callbacks: {
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ async jwt({
100
+ token,
101
+ user,
102
+ trigger,
103
+ session: sessionUpdate
104
+ }) {
105
+ if (user) {
106
+ return {
107
+ ...token,
108
+ sub: user.sub,
109
+ email: user.email,
110
+ nickname: user.nickname,
111
+ accessToken: user.accessToken,
112
+ refreshToken: user.refreshToken,
113
+ idToken: user.idToken,
114
+ accessTokenExpires: user.accessTokenExpires,
115
+ zkEvm: user.zkEvm
116
+ };
117
+ }
118
+ if (trigger === "update" && sessionUpdate) {
119
+ const update = sessionUpdate;
120
+ return {
121
+ ...token,
122
+ ...update.accessToken ? { accessToken: update.accessToken } : {},
123
+ ...update.refreshToken ? { refreshToken: update.refreshToken } : {},
124
+ ...update.idToken ? { idToken: update.idToken } : {},
125
+ ...update.accessTokenExpires ? { accessTokenExpires: update.accessTokenExpires } : {},
126
+ ...update.zkEvm ? { zkEvm: update.zkEvm } : {},
127
+ // Clear any stale error when valid tokens are synced from client-side
128
+ error: void 0
129
+ };
130
+ }
131
+ if (!isTokenExpired(token.accessTokenExpires)) {
132
+ return token;
133
+ }
134
+ return {
135
+ ...token,
136
+ error: "TokenExpired"
137
+ };
138
+ },
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ async session({ session, token }) {
141
+ return {
142
+ ...session,
143
+ user: {
144
+ ...session.user,
145
+ sub: token.sub,
146
+ email: token.email,
147
+ nickname: token.nickname
148
+ },
149
+ accessToken: token.accessToken,
150
+ refreshToken: token.refreshToken,
151
+ idToken: token.idToken,
152
+ accessTokenExpires: token.accessTokenExpires,
153
+ zkEvm: token.zkEvm,
154
+ ...token.error && { error: token.error }
155
+ };
156
+ }
157
+ },
158
+ session: {
159
+ strategy: "jwt",
160
+ // Session max age in seconds (365 days default)
161
+ maxAge: DEFAULT_SESSION_MAX_AGE_SECONDS
162
+ }
163
+ };
164
+ }
165
+ var createAuthOptions = createAuthConfig;
166
+
167
+ // src/utils/pathMatch.ts
168
+ function matchPathPrefix(pathname, pattern) {
169
+ if (pathname === pattern) return true;
170
+ const prefix = pattern.endsWith("/") ? pattern : `${pattern}/`;
171
+ return pathname.startsWith(prefix);
172
+ }
173
+
174
+ // src/index.ts
175
+ var NextAuth = NextAuthImport.default || NextAuthImport;
176
+ function createImmutableAuth(config, overrides) {
177
+ const baseConfig = createAuthConfig(config);
178
+ if (!overrides) {
179
+ return NextAuth(baseConfig);
180
+ }
181
+ const { callbacks: overrideCallbacks, ...otherOverrides } = overrides;
182
+ const composedCallbacks = { ...baseConfig.callbacks };
183
+ if (overrideCallbacks) {
184
+ if (overrideCallbacks.jwt) {
185
+ const baseJwt = baseConfig.callbacks?.jwt;
186
+ const userJwt = overrideCallbacks.jwt;
187
+ composedCallbacks.jwt = async (params) => {
188
+ const result = baseJwt ? await baseJwt(params) : params.token;
189
+ return userJwt({ ...params, token: result });
190
+ };
191
+ }
192
+ if (overrideCallbacks.session) {
193
+ const baseSession = baseConfig.callbacks?.session;
194
+ const userSession = overrideCallbacks.session;
195
+ composedCallbacks.session = async (params) => {
196
+ const result = baseSession ? await baseSession(params) : params.session;
197
+ return userSession({ ...params, session: result });
198
+ };
199
+ }
200
+ if (overrideCallbacks.signIn) {
201
+ composedCallbacks.signIn = overrideCallbacks.signIn;
202
+ }
203
+ if (overrideCallbacks.redirect) {
204
+ composedCallbacks.redirect = overrideCallbacks.redirect;
205
+ }
206
+ if (overrideCallbacks.authorized) {
207
+ composedCallbacks.authorized = overrideCallbacks.authorized;
208
+ }
209
+ }
210
+ const mergedConfig = {
211
+ ...baseConfig,
212
+ ...otherOverrides,
213
+ callbacks: composedCallbacks
214
+ };
215
+ return NextAuth(mergedConfig);
216
+ }
217
+ async function getAuthProps(auth) {
218
+ const session = await auth();
219
+ if (!session) {
220
+ return { session: null, ssr: false };
221
+ }
222
+ if (session.error === "TokenExpired") {
223
+ return { session: null, ssr: false };
224
+ }
225
+ if (session.error) {
226
+ return { session: null, ssr: false, authError: session.error };
227
+ }
228
+ return { session, ssr: true };
229
+ }
230
+ async function getAuthenticatedData(auth, fetcher) {
231
+ const session = await auth();
232
+ if (!session) {
233
+ return { session: null, ssr: false, data: null };
234
+ }
235
+ if (session.error === "TokenExpired") {
236
+ return { session: null, ssr: false, data: null };
237
+ }
238
+ if (session.error) {
239
+ return {
240
+ session: null,
241
+ ssr: false,
242
+ data: null,
243
+ authError: session.error
244
+ };
245
+ }
246
+ try {
247
+ const data = await fetcher(session.accessToken);
248
+ return { session, ssr: true, data };
249
+ } catch (err) {
250
+ const errorMessage = err instanceof Error ? err.message : String(err);
251
+ return {
252
+ session,
253
+ ssr: true,
254
+ data: null,
255
+ fetchError: errorMessage
256
+ };
257
+ }
258
+ }
259
+ async function getValidSession(auth) {
260
+ const session = await auth();
261
+ if (!session) {
262
+ return { status: "unauthenticated", session: null };
263
+ }
264
+ if (!session.error) {
265
+ return { status: "authenticated", session };
266
+ }
267
+ if (session.error === "TokenExpired") {
268
+ return { status: "token_expired", session };
269
+ }
270
+ return { status: "error", session, error: session.error };
271
+ }
272
+ function createProtectedDataFetcher(auth, onAuthError) {
273
+ return async function getProtectedData(fetcher) {
274
+ const result = await getAuthenticatedData(auth, fetcher);
275
+ if (result.authError) {
276
+ onAuthError(result.authError);
277
+ }
278
+ const { authError: handledAuthError, ...props } = result;
279
+ return props;
280
+ };
281
+ }
282
+ function createProtectedAuthProps(auth, onAuthError) {
283
+ return async function getProtectedAuth() {
284
+ const result = await getAuthProps(auth);
285
+ if (result.authError) {
286
+ onAuthError(result.authError);
287
+ }
288
+ const { authError: handledAuthError, ...props } = result;
289
+ return props;
290
+ };
291
+ }
292
+ function createProtectedFetchers(auth, onAuthError) {
293
+ return {
294
+ getAuthProps: createProtectedAuthProps(auth, onAuthError),
295
+ getData: createProtectedDataFetcher(auth, onAuthError)
296
+ };
297
+ }
298
+ async function withServerAuth(auth, serverRender, options = {}) {
299
+ const result = await getValidSession(auth);
300
+ switch (result.status) {
301
+ case "authenticated":
302
+ return serverRender(result.session);
303
+ case "token_expired":
304
+ if (options.onTokenExpired !== void 0) {
305
+ return typeof options.onTokenExpired === "function" ? options.onTokenExpired() : options.onTokenExpired;
306
+ }
307
+ return serverRender(result.session);
308
+ case "unauthenticated":
309
+ if (options.onUnauthenticated !== void 0) {
310
+ return typeof options.onUnauthenticated === "function" ? options.onUnauthenticated() : options.onUnauthenticated;
311
+ }
312
+ throw new Error("Unauthorized: No active session");
313
+ case "error":
314
+ if (options.onError !== void 0) {
315
+ return typeof options.onError === "function" ? options.onError(result.error) : options.onError;
316
+ }
317
+ throw new Error(`Unauthorized: ${result.error}`);
318
+ default:
319
+ throw new Error("Unknown auth state");
320
+ }
321
+ }
322
+ function createAuthMiddleware(auth, options = {}) {
323
+ const { loginUrl = "/login", protectedPaths, publicPaths } = options;
324
+ return async function middleware(request) {
325
+ const { pathname } = request.nextUrl;
326
+ if (publicPaths) {
327
+ const isPublic = publicPaths.some((pattern) => {
328
+ if (typeof pattern === "string") {
329
+ return matchPathPrefix(pathname, pattern);
330
+ }
331
+ return pattern.test(pathname);
332
+ });
333
+ if (isPublic) {
334
+ return NextResponse.next();
335
+ }
336
+ }
337
+ if (protectedPaths) {
338
+ const isProtected = protectedPaths.some((pattern) => {
339
+ if (typeof pattern === "string") {
340
+ return matchPathPrefix(pathname, pattern);
341
+ }
342
+ return pattern.test(pathname);
343
+ });
344
+ if (!isProtected) {
345
+ return NextResponse.next();
346
+ }
347
+ }
348
+ const session = await auth();
349
+ if (!session) {
350
+ const url = new URL(loginUrl, request.url);
351
+ const returnTo = request.nextUrl.search ? `${pathname}${request.nextUrl.search}` : pathname;
352
+ url.searchParams.set("returnTo", returnTo);
353
+ return NextResponse.redirect(url);
354
+ }
355
+ if (session.error && session.error !== "TokenExpired") {
356
+ const url = new URL(loginUrl, request.url);
357
+ const returnTo = request.nextUrl.search ? `${pathname}${request.nextUrl.search}` : pathname;
358
+ url.searchParams.set("returnTo", returnTo);
359
+ url.searchParams.set("error", session.error);
360
+ return NextResponse.redirect(url);
361
+ }
362
+ return NextResponse.next();
363
+ };
364
+ }
365
+ function withAuth(auth, handler) {
366
+ return async (...args) => {
367
+ const session = await auth();
368
+ if (!session) {
369
+ throw new Error("Unauthorized: No active session");
370
+ }
371
+ if (session.error && session.error !== "TokenExpired") {
372
+ throw new Error(`Unauthorized: ${session.error}`);
373
+ }
374
+ return handler(session, ...args);
375
+ };
376
+ }
377
+ export {
378
+ createAuthConfig,
379
+ createAuthMiddleware,
380
+ createAuthOptions,
381
+ createImmutableAuth,
382
+ createProtectedAuthProps,
383
+ createProtectedDataFetcher,
384
+ createProtectedFetchers,
385
+ getAuthProps,
386
+ getAuthenticatedData,
387
+ getValidSession,
388
+ withAuth,
389
+ withServerAuth
390
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Check if the access token is expired or about to expire
3
+ * Returns true if token expires within the buffer time (default 60 seconds)
4
+ *
5
+ * @remarks
6
+ * If accessTokenExpires is not a valid number (undefined, null, NaN),
7
+ * returns true to trigger a refresh as a safety measure.
8
+ */
9
+ export declare function isTokenExpired(accessTokenExpires: number, bufferSeconds?: number): boolean;