@imtbl/sdk 2.12.5-alpha.1 → 2.12.5-alpha.11

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.
@@ -1 +1,373 @@
1
- export * from '@imtbl/auth-nextjs/server';
1
+ import { NextResponse } from 'next/server';
2
+ import NextAuthImport from 'next-auth';
3
+ import CredentialsImport from 'next-auth/providers/credentials';
4
+
5
+ // ../packages/auth-nextjs/dist/node/server/index.js
6
+ function matchPathPrefix(pathname, pattern) {
7
+ if (pathname === pattern) return true;
8
+ const prefix = pattern.endsWith("/") ? pattern : `${pattern}/`;
9
+ return pathname.startsWith(prefix);
10
+ }
11
+ var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
12
+ var IMMUTABLE_PROVIDER_ID = "immutable";
13
+ var TOKEN_EXPIRY_BUFFER_SECONDS = 60;
14
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
15
+ function isTokenExpired(accessTokenExpires, bufferSeconds = TOKEN_EXPIRY_BUFFER_SECONDS) {
16
+ if (typeof accessTokenExpires !== "number" || Number.isNaN(accessTokenExpires)) {
17
+ return true;
18
+ }
19
+ return Date.now() >= accessTokenExpires - bufferSeconds * 1e3;
20
+ }
21
+ var Credentials = CredentialsImport.default || CredentialsImport;
22
+ async function validateTokens(accessToken, authDomain) {
23
+ try {
24
+ const response = await fetch(`${authDomain}/userinfo`, {
25
+ method: "GET",
26
+ headers: {
27
+ Authorization: `Bearer ${accessToken}`
28
+ }
29
+ });
30
+ if (!response.ok) {
31
+ console.error("[auth-nextjs] Token validation failed:", response.status, response.statusText);
32
+ return null;
33
+ }
34
+ return await response.json();
35
+ } catch (error) {
36
+ console.error("[auth-nextjs] Token validation error:", error);
37
+ return null;
38
+ }
39
+ }
40
+ function createAuthConfig(config) {
41
+ const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
42
+ return {
43
+ providers: [
44
+ Credentials({
45
+ id: IMMUTABLE_PROVIDER_ID,
46
+ name: "Immutable",
47
+ credentials: {
48
+ tokens: { label: "Tokens", type: "text" }
49
+ },
50
+ async authorize(credentials) {
51
+ if (!credentials?.tokens || typeof credentials.tokens !== "string") {
52
+ return null;
53
+ }
54
+ let tokenData;
55
+ try {
56
+ tokenData = JSON.parse(credentials.tokens);
57
+ } catch (error) {
58
+ console.error("[auth-nextjs] Failed to parse token data:", error);
59
+ return null;
60
+ }
61
+ 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)) {
62
+ console.error("[auth-nextjs] Invalid token data structure - missing required fields");
63
+ return null;
64
+ }
65
+ const userInfo = await validateTokens(tokenData.accessToken, authDomain);
66
+ if (!userInfo) {
67
+ console.error("[auth-nextjs] Token validation failed - rejecting authentication");
68
+ return null;
69
+ }
70
+ if (userInfo.sub !== tokenData.profile.sub) {
71
+ console.error(
72
+ "[auth-nextjs] User ID mismatch - userinfo sub:",
73
+ userInfo.sub,
74
+ "provided sub:",
75
+ tokenData.profile.sub
76
+ );
77
+ return null;
78
+ }
79
+ return {
80
+ id: userInfo.sub,
81
+ sub: userInfo.sub,
82
+ email: userInfo.email ?? tokenData.profile.email,
83
+ nickname: userInfo.nickname ?? tokenData.profile.nickname,
84
+ accessToken: tokenData.accessToken,
85
+ refreshToken: tokenData.refreshToken,
86
+ idToken: tokenData.idToken,
87
+ accessTokenExpires: tokenData.accessTokenExpires,
88
+ zkEvm: tokenData.zkEvm
89
+ };
90
+ }
91
+ })
92
+ ],
93
+ callbacks: {
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ async jwt({
96
+ token,
97
+ user,
98
+ trigger,
99
+ session: sessionUpdate
100
+ }) {
101
+ if (user) {
102
+ return {
103
+ ...token,
104
+ sub: user.sub,
105
+ email: user.email,
106
+ nickname: user.nickname,
107
+ accessToken: user.accessToken,
108
+ refreshToken: user.refreshToken,
109
+ idToken: user.idToken,
110
+ accessTokenExpires: user.accessTokenExpires,
111
+ zkEvm: user.zkEvm
112
+ };
113
+ }
114
+ if (trigger === "update" && sessionUpdate) {
115
+ const update = sessionUpdate;
116
+ return {
117
+ ...token,
118
+ ...update.accessToken ? { accessToken: update.accessToken } : {},
119
+ ...update.refreshToken ? { refreshToken: update.refreshToken } : {},
120
+ ...update.idToken ? { idToken: update.idToken } : {},
121
+ ...update.accessTokenExpires ? { accessTokenExpires: update.accessTokenExpires } : {},
122
+ ...update.zkEvm ? { zkEvm: update.zkEvm } : {},
123
+ // Clear any stale error when valid tokens are synced from client-side
124
+ error: void 0
125
+ };
126
+ }
127
+ if (!isTokenExpired(token.accessTokenExpires)) {
128
+ return token;
129
+ }
130
+ return {
131
+ ...token,
132
+ error: "TokenExpired"
133
+ };
134
+ },
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ async session({ session, token }) {
137
+ return {
138
+ ...session,
139
+ user: {
140
+ ...session.user,
141
+ sub: token.sub,
142
+ email: token.email,
143
+ nickname: token.nickname
144
+ },
145
+ accessToken: token.accessToken,
146
+ refreshToken: token.refreshToken,
147
+ idToken: token.idToken,
148
+ accessTokenExpires: token.accessTokenExpires,
149
+ zkEvm: token.zkEvm,
150
+ ...token.error && { error: token.error }
151
+ };
152
+ }
153
+ },
154
+ session: {
155
+ strategy: "jwt",
156
+ // Session max age in seconds (365 days default)
157
+ maxAge: DEFAULT_SESSION_MAX_AGE_SECONDS
158
+ }
159
+ };
160
+ }
161
+ var createAuthOptions = createAuthConfig;
162
+ var NextAuth = NextAuthImport.default || NextAuthImport;
163
+ function createImmutableAuth(config, overrides) {
164
+ const authConfig = createAuthConfig(config);
165
+ if (!overrides) {
166
+ return NextAuth(authConfig);
167
+ }
168
+ const composedCallbacks = {
169
+ ...authConfig.callbacks
170
+ };
171
+ if (overrides.callbacks) {
172
+ if (overrides.callbacks.jwt) {
173
+ const internalJwt = authConfig.callbacks?.jwt;
174
+ const userJwt = overrides.callbacks.jwt;
175
+ composedCallbacks.jwt = async (params) => {
176
+ const token = internalJwt ? await internalJwt(params) : params.token;
177
+ return userJwt({ ...params, token });
178
+ };
179
+ }
180
+ if (overrides.callbacks.session) {
181
+ const internalSession = authConfig.callbacks?.session;
182
+ const userSession = overrides.callbacks.session;
183
+ composedCallbacks.session = async (params) => {
184
+ const session = internalSession ? await internalSession(params) : params.session;
185
+ return userSession({ ...params, session });
186
+ };
187
+ }
188
+ if (overrides.callbacks.signIn) {
189
+ composedCallbacks.signIn = overrides.callbacks.signIn;
190
+ }
191
+ if (overrides.callbacks.redirect) {
192
+ composedCallbacks.redirect = overrides.callbacks.redirect;
193
+ }
194
+ if (overrides.callbacks.authorized) {
195
+ composedCallbacks.authorized = overrides.callbacks.authorized;
196
+ }
197
+ }
198
+ const mergedSession = overrides.session ? {
199
+ ...authConfig.session,
200
+ ...overrides.session,
201
+ // Always enforce JWT strategy - this is required for token storage/refresh
202
+ strategy: "jwt"
203
+ } : authConfig.session;
204
+ const mergedConfig = {
205
+ ...authConfig,
206
+ ...overrides,
207
+ callbacks: composedCallbacks,
208
+ session: mergedSession
209
+ };
210
+ return NextAuth(mergedConfig);
211
+ }
212
+ async function getAuthProps(auth) {
213
+ const session = await auth();
214
+ if (!session) {
215
+ return { session: null, ssr: false };
216
+ }
217
+ if (session.error === "TokenExpired") {
218
+ return { session: null, ssr: false };
219
+ }
220
+ if (session.error) {
221
+ return { session: null, ssr: false, authError: session.error };
222
+ }
223
+ return { session, ssr: true };
224
+ }
225
+ async function getAuthenticatedData(auth, fetcher) {
226
+ const session = await auth();
227
+ if (!session) {
228
+ return { session: null, ssr: false, data: null };
229
+ }
230
+ if (session.error === "TokenExpired") {
231
+ return { session: null, ssr: false, data: null };
232
+ }
233
+ if (session.error) {
234
+ return {
235
+ session: null,
236
+ ssr: false,
237
+ data: null,
238
+ authError: session.error
239
+ };
240
+ }
241
+ try {
242
+ const data = await fetcher(session.accessToken);
243
+ return { session, ssr: true, data };
244
+ } catch (err) {
245
+ const errorMessage = err instanceof Error ? err.message : String(err);
246
+ return {
247
+ session,
248
+ ssr: true,
249
+ data: null,
250
+ fetchError: errorMessage
251
+ };
252
+ }
253
+ }
254
+ async function getValidSession(auth) {
255
+ const session = await auth();
256
+ if (!session) {
257
+ return { status: "unauthenticated", session: null };
258
+ }
259
+ if (!session.error) {
260
+ return { status: "authenticated", session };
261
+ }
262
+ if (session.error === "TokenExpired") {
263
+ return { status: "token_expired", session };
264
+ }
265
+ return { status: "error", session, error: session.error };
266
+ }
267
+ function createProtectedDataFetcher(auth, onAuthError) {
268
+ return async function getProtectedData(fetcher) {
269
+ const result = await getAuthenticatedData(auth, fetcher);
270
+ if (result.authError) {
271
+ onAuthError(result.authError);
272
+ }
273
+ const { authError: handledAuthError, ...props } = result;
274
+ return props;
275
+ };
276
+ }
277
+ function createProtectedAuthProps(auth, onAuthError) {
278
+ return async function getProtectedAuth() {
279
+ const result = await getAuthProps(auth);
280
+ if (result.authError) {
281
+ onAuthError(result.authError);
282
+ }
283
+ const { authError: handledAuthError, ...props } = result;
284
+ return props;
285
+ };
286
+ }
287
+ function createProtectedFetchers(auth, onAuthError) {
288
+ return {
289
+ getAuthProps: createProtectedAuthProps(auth, onAuthError),
290
+ getData: createProtectedDataFetcher(auth, onAuthError)
291
+ };
292
+ }
293
+ async function withServerAuth(auth, serverRender, options = {}) {
294
+ const result = await getValidSession(auth);
295
+ switch (result.status) {
296
+ case "authenticated":
297
+ return serverRender(result.session);
298
+ case "token_expired":
299
+ if (options.onTokenExpired !== void 0) {
300
+ return typeof options.onTokenExpired === "function" ? options.onTokenExpired() : options.onTokenExpired;
301
+ }
302
+ return serverRender(result.session);
303
+ case "unauthenticated":
304
+ if (options.onUnauthenticated !== void 0) {
305
+ return typeof options.onUnauthenticated === "function" ? options.onUnauthenticated() : options.onUnauthenticated;
306
+ }
307
+ throw new Error("Unauthorized: No active session");
308
+ case "error":
309
+ if (options.onError !== void 0) {
310
+ return typeof options.onError === "function" ? options.onError(result.error) : options.onError;
311
+ }
312
+ throw new Error(`Unauthorized: ${result.error}`);
313
+ default:
314
+ throw new Error("Unknown auth state");
315
+ }
316
+ }
317
+ function createAuthMiddleware(auth, options = {}) {
318
+ const { loginUrl = "/login", protectedPaths, publicPaths } = options;
319
+ return async function middleware(request) {
320
+ const { pathname } = request.nextUrl;
321
+ if (publicPaths) {
322
+ const isPublic = publicPaths.some((pattern) => {
323
+ if (typeof pattern === "string") {
324
+ return matchPathPrefix(pathname, pattern);
325
+ }
326
+ return pattern.test(pathname);
327
+ });
328
+ if (isPublic) {
329
+ return NextResponse.next();
330
+ }
331
+ }
332
+ if (protectedPaths) {
333
+ const isProtected = protectedPaths.some((pattern) => {
334
+ if (typeof pattern === "string") {
335
+ return matchPathPrefix(pathname, pattern);
336
+ }
337
+ return pattern.test(pathname);
338
+ });
339
+ if (!isProtected) {
340
+ return NextResponse.next();
341
+ }
342
+ }
343
+ const session = await auth();
344
+ if (!session) {
345
+ const url = new URL(loginUrl, request.url);
346
+ const returnTo = request.nextUrl.search ? `${pathname}${request.nextUrl.search}` : pathname;
347
+ url.searchParams.set("returnTo", returnTo);
348
+ return NextResponse.redirect(url);
349
+ }
350
+ if (session.error && session.error !== "TokenExpired") {
351
+ const url = new URL(loginUrl, request.url);
352
+ const returnTo = request.nextUrl.search ? `${pathname}${request.nextUrl.search}` : pathname;
353
+ url.searchParams.set("returnTo", returnTo);
354
+ url.searchParams.set("error", session.error);
355
+ return NextResponse.redirect(url);
356
+ }
357
+ return NextResponse.next();
358
+ };
359
+ }
360
+ function withAuth(auth, handler) {
361
+ return async (...args) => {
362
+ const session = await auth();
363
+ if (!session) {
364
+ throw new Error("Unauthorized: No active session");
365
+ }
366
+ if (session.error && session.error !== "TokenExpired") {
367
+ throw new Error(`Unauthorized: ${session.error}`);
368
+ }
369
+ return handler(session, ...args);
370
+ };
371
+ }
372
+
373
+ export { createAuthConfig, createAuthMiddleware, createAuthOptions, createImmutableAuth, createProtectedAuthProps, createProtectedDataFetcher, createProtectedFetchers, getAuthProps, getAuthenticatedData, getValidSession, withAuth, withServerAuth };