@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.
- package/.eslintrc.cjs +17 -0
- package/LICENSE.md +176 -0
- package/dist/node/config.d.ts +21 -0
- package/dist/node/constants.d.ts +42 -0
- package/dist/node/index.cjs +436 -0
- package/dist/node/index.d.ts +301 -0
- package/dist/node/index.js +390 -0
- package/dist/node/refresh.d.ts +9 -0
- package/dist/node/types.d.ts +111 -0
- package/dist/node/utils/pathMatch.d.ts +10 -0
- package/jest.config.ts +16 -0
- package/package.json +60 -0
- package/src/config.ts +243 -0
- package/src/constants.ts +51 -0
- package/src/index.ts +662 -0
- package/src/refresh.ts +21 -0
- package/src/types.ts +124 -0
- package/src/utils/pathMatch.ts +16 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +16 -0
- package/tsconfig.types.json +8 -0
- package/tsup.config.ts +29 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side types for @imtbl/auth-next-server
|
|
3
|
+
*/
|
|
4
|
+
import type { DefaultSession } from 'next-auth';
|
|
5
|
+
/**
|
|
6
|
+
* zkEVM wallet information for module augmentation
|
|
7
|
+
*/
|
|
8
|
+
interface ZkEvmInfo {
|
|
9
|
+
ethAddress: string;
|
|
10
|
+
userAdminAddress: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Auth.js v5 module augmentation to add Immutable-specific fields
|
|
14
|
+
* This extends the Session type to include our custom fields
|
|
15
|
+
*/
|
|
16
|
+
declare module 'next-auth' {
|
|
17
|
+
interface Session extends DefaultSession {
|
|
18
|
+
user: {
|
|
19
|
+
sub: string;
|
|
20
|
+
email?: string;
|
|
21
|
+
nickname?: string;
|
|
22
|
+
} & DefaultSession['user'];
|
|
23
|
+
accessToken: string;
|
|
24
|
+
refreshToken?: string;
|
|
25
|
+
idToken?: string;
|
|
26
|
+
accessTokenExpires: number;
|
|
27
|
+
zkEvm?: ZkEvmInfo;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
interface User {
|
|
31
|
+
id: string;
|
|
32
|
+
sub: string;
|
|
33
|
+
email?: string | null;
|
|
34
|
+
nickname?: string;
|
|
35
|
+
accessToken: string;
|
|
36
|
+
refreshToken?: string;
|
|
37
|
+
idToken?: string;
|
|
38
|
+
accessTokenExpires: number;
|
|
39
|
+
zkEvm?: ZkEvmInfo;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configuration options for Immutable authentication
|
|
44
|
+
*/
|
|
45
|
+
export interface ImmutableAuthConfig {
|
|
46
|
+
/**
|
|
47
|
+
* Your Immutable application client ID
|
|
48
|
+
*/
|
|
49
|
+
clientId: string;
|
|
50
|
+
/**
|
|
51
|
+
* The OAuth redirect URI configured in your Immutable Hub project
|
|
52
|
+
*/
|
|
53
|
+
redirectUri: string;
|
|
54
|
+
/**
|
|
55
|
+
* OAuth audience (default: "platform_api")
|
|
56
|
+
*/
|
|
57
|
+
audience?: string;
|
|
58
|
+
/**
|
|
59
|
+
* OAuth scopes (default: "openid profile email offline_access transact")
|
|
60
|
+
*/
|
|
61
|
+
scope?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The Immutable authentication domain (default: "https://auth.immutable.com")
|
|
64
|
+
*/
|
|
65
|
+
authenticationDomain?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Token data passed from client to server during authentication
|
|
69
|
+
*/
|
|
70
|
+
export interface ImmutableTokenData {
|
|
71
|
+
accessToken: string;
|
|
72
|
+
refreshToken?: string;
|
|
73
|
+
idToken?: string;
|
|
74
|
+
accessTokenExpires: number;
|
|
75
|
+
profile: {
|
|
76
|
+
sub: string;
|
|
77
|
+
email?: string;
|
|
78
|
+
nickname?: string;
|
|
79
|
+
};
|
|
80
|
+
zkEvm?: {
|
|
81
|
+
ethAddress: string;
|
|
82
|
+
userAdminAddress: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Response from the userinfo endpoint
|
|
87
|
+
*/
|
|
88
|
+
export interface UserInfoResponse {
|
|
89
|
+
sub: string;
|
|
90
|
+
email?: string;
|
|
91
|
+
email_verified?: boolean;
|
|
92
|
+
nickname?: string;
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* zkEVM user data stored in session
|
|
97
|
+
*/
|
|
98
|
+
export interface ZkEvmUser {
|
|
99
|
+
ethAddress: string;
|
|
100
|
+
userAdminAddress: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Immutable user data structure
|
|
104
|
+
*/
|
|
105
|
+
export interface ImmutableUser {
|
|
106
|
+
sub: string;
|
|
107
|
+
email?: string;
|
|
108
|
+
nickname?: string;
|
|
109
|
+
zkEvm?: ZkEvmUser;
|
|
110
|
+
}
|
|
111
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if pathname matches a string pattern as a path prefix.
|
|
3
|
+
* Ensures proper path boundary checking: '/api' matches '/api' and '/api/users'
|
|
4
|
+
* but NOT '/apiversion' or '/api-docs'.
|
|
5
|
+
*
|
|
6
|
+
* @param pathname - The URL pathname to check
|
|
7
|
+
* @param pattern - The string pattern to match against
|
|
8
|
+
* @returns true if pathname matches the pattern with proper path boundaries
|
|
9
|
+
*/
|
|
10
|
+
export declare function matchPathPrefix(pathname: string, pattern: string): boolean;
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Config } from 'jest';
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
clearMocks: true,
|
|
5
|
+
coverageProvider: 'v8',
|
|
6
|
+
moduleDirectories: ['node_modules', 'src'],
|
|
7
|
+
testEnvironment: 'node',
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.(t|j)sx?$': '@swc/jest',
|
|
10
|
+
},
|
|
11
|
+
transformIgnorePatterns: [],
|
|
12
|
+
restoreMocks: true,
|
|
13
|
+
roots: ['<rootDir>/src'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@imtbl/auth-next-server",
|
|
3
|
+
"version": "2.12.5-alpha.13",
|
|
4
|
+
"description": "Immutable Auth.js v5 integration for Next.js - Server-side utilities",
|
|
5
|
+
"author": "Immutable",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": "immutable/ts-immutable-sdk.git",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/node/index.cjs",
|
|
13
|
+
"module": "./dist/node/index.js",
|
|
14
|
+
"types": "./dist/node/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"development": {
|
|
18
|
+
"types": "./src/index.ts",
|
|
19
|
+
"require": "./dist/node/index.cjs",
|
|
20
|
+
"default": "./dist/node/index.js"
|
|
21
|
+
},
|
|
22
|
+
"default": {
|
|
23
|
+
"types": "./dist/node/index.d.ts",
|
|
24
|
+
"require": "./dist/node/index.cjs",
|
|
25
|
+
"default": "./dist/node/index.js"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"next": "^14.2.0 || ^15.0.0",
|
|
31
|
+
"next-auth": "^5.0.0-beta.25"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"next": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"next-auth": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@swc/core": "^1.4.2",
|
|
43
|
+
"@swc/jest": "^0.2.37",
|
|
44
|
+
"@types/jest": "^29.5.12",
|
|
45
|
+
"@types/node": "^22.10.7",
|
|
46
|
+
"eslint": "^8.56.0",
|
|
47
|
+
"jest": "^29.7.0",
|
|
48
|
+
"next": "^15.1.6",
|
|
49
|
+
"next-auth": "^5.0.0-beta.30",
|
|
50
|
+
"tsup": "^8.3.0",
|
|
51
|
+
"typescript": "^5.6.2"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup && pnpm build:types",
|
|
55
|
+
"build:types": "tsc --project tsconfig.types.json",
|
|
56
|
+
"clean": "rm -rf dist",
|
|
57
|
+
"lint": "eslint src/**/*.ts --max-warnings=0",
|
|
58
|
+
"test": "jest --passWithNoTests"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// NextAuthConfig type from next-auth v5
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore - Type exists in next-auth v5 but TS resolver may use stale types
|
|
4
|
+
import type { NextAuthConfig } from 'next-auth';
|
|
5
|
+
import CredentialsImport from 'next-auth/providers/credentials';
|
|
6
|
+
import type { ImmutableAuthConfig, ImmutableTokenData, UserInfoResponse } from './types';
|
|
7
|
+
import { isTokenExpired } from './refresh';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_AUTH_DOMAIN,
|
|
10
|
+
IMMUTABLE_PROVIDER_ID,
|
|
11
|
+
DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
12
|
+
} from './constants';
|
|
13
|
+
|
|
14
|
+
// Handle ESM/CJS interop - in some bundler configurations, the default export
|
|
15
|
+
// may be nested under a 'default' property
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const Credentials = ((CredentialsImport as any).default || CredentialsImport) as typeof CredentialsImport;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate tokens by calling the userinfo endpoint.
|
|
21
|
+
* This is the standard OAuth 2.0 way to validate access tokens server-side.
|
|
22
|
+
* The auth server validates signature, issuer, audience, and expiry.
|
|
23
|
+
*
|
|
24
|
+
* @param accessToken - The access token to validate
|
|
25
|
+
* @param authDomain - The authentication domain
|
|
26
|
+
* @returns The user info if valid, null otherwise
|
|
27
|
+
*/
|
|
28
|
+
async function validateTokens(
|
|
29
|
+
accessToken: string,
|
|
30
|
+
authDomain: string,
|
|
31
|
+
): Promise<UserInfoResponse | null> {
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(`${authDomain}/userinfo`, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${accessToken}`,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.error('[auth-next-server] Token validation failed:', response.status, response.statusText);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return await response.json();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.error('[auth-next-server] Token validation error:', error);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create Auth.js v5 configuration for Immutable authentication
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // lib/auth.ts
|
|
60
|
+
* import NextAuth from "next-auth";
|
|
61
|
+
* import { createAuthConfig } from "@imtbl/auth-next-server";
|
|
62
|
+
*
|
|
63
|
+
* const config = {
|
|
64
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
65
|
+
* redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
|
|
66
|
+
* };
|
|
67
|
+
*
|
|
68
|
+
* export const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig(config));
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function createAuthConfig(config: ImmutableAuthConfig): NextAuthConfig {
|
|
72
|
+
const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
providers: [
|
|
76
|
+
Credentials({
|
|
77
|
+
id: IMMUTABLE_PROVIDER_ID,
|
|
78
|
+
name: 'Immutable',
|
|
79
|
+
credentials: {
|
|
80
|
+
tokens: { label: 'Tokens', type: 'text' },
|
|
81
|
+
},
|
|
82
|
+
async authorize(credentials) {
|
|
83
|
+
if (!credentials?.tokens || typeof credentials.tokens !== 'string') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let tokenData: ImmutableTokenData;
|
|
88
|
+
try {
|
|
89
|
+
tokenData = JSON.parse(credentials.tokens);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.error('[auth-next-server] Failed to parse token data:', error);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate required fields exist to prevent TypeError on malformed requests
|
|
97
|
+
// accessTokenExpires must be a valid number to ensure isTokenExpired() works correctly
|
|
98
|
+
// (NaN comparisons always return false, which would prevent token refresh)
|
|
99
|
+
if (
|
|
100
|
+
!tokenData.accessToken
|
|
101
|
+
|| typeof tokenData.accessToken !== 'string'
|
|
102
|
+
|| !tokenData.profile
|
|
103
|
+
|| typeof tokenData.profile !== 'object'
|
|
104
|
+
|| !tokenData.profile.sub
|
|
105
|
+
|| typeof tokenData.profile.sub !== 'string'
|
|
106
|
+
|| typeof tokenData.accessTokenExpires !== 'number'
|
|
107
|
+
|| Number.isNaN(tokenData.accessTokenExpires)
|
|
108
|
+
) {
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.error('[auth-next-server] Invalid token data structure - missing required fields');
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate tokens server-side via userinfo endpoint.
|
|
115
|
+
// This is the standard OAuth 2.0 way - the auth server validates the token.
|
|
116
|
+
const userInfo = await validateTokens(tokenData.accessToken, authDomain);
|
|
117
|
+
if (!userInfo) {
|
|
118
|
+
// eslint-disable-next-line no-console
|
|
119
|
+
console.error('[auth-next-server] Token validation failed - rejecting authentication');
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Verify the user ID (sub) from userinfo matches the client-provided profile.
|
|
124
|
+
// This prevents spoofing a different user ID with a valid token.
|
|
125
|
+
if (userInfo.sub !== tokenData.profile.sub) {
|
|
126
|
+
// eslint-disable-next-line no-console
|
|
127
|
+
console.error(
|
|
128
|
+
'[auth-next-server] User ID mismatch - userinfo sub:',
|
|
129
|
+
userInfo.sub,
|
|
130
|
+
'provided sub:',
|
|
131
|
+
tokenData.profile.sub,
|
|
132
|
+
);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Return user object with validated data
|
|
137
|
+
return {
|
|
138
|
+
id: userInfo.sub,
|
|
139
|
+
sub: userInfo.sub,
|
|
140
|
+
email: userInfo.email ?? tokenData.profile.email,
|
|
141
|
+
nickname: userInfo.nickname ?? tokenData.profile.nickname,
|
|
142
|
+
accessToken: tokenData.accessToken,
|
|
143
|
+
refreshToken: tokenData.refreshToken,
|
|
144
|
+
idToken: tokenData.idToken,
|
|
145
|
+
accessTokenExpires: tokenData.accessTokenExpires,
|
|
146
|
+
zkEvm: tokenData.zkEvm,
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
],
|
|
151
|
+
|
|
152
|
+
callbacks: {
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
async jwt({
|
|
155
|
+
token, user, trigger, session: sessionUpdate,
|
|
156
|
+
}: any) {
|
|
157
|
+
// Initial sign in - store all token data
|
|
158
|
+
if (user) {
|
|
159
|
+
return {
|
|
160
|
+
...token,
|
|
161
|
+
sub: user.sub,
|
|
162
|
+
email: user.email,
|
|
163
|
+
nickname: user.nickname,
|
|
164
|
+
accessToken: user.accessToken,
|
|
165
|
+
refreshToken: user.refreshToken,
|
|
166
|
+
idToken: user.idToken,
|
|
167
|
+
accessTokenExpires: user.accessTokenExpires,
|
|
168
|
+
zkEvm: user.zkEvm,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Handle session update (for client-side token sync)
|
|
173
|
+
// When client-side Auth refreshes tokens via TOKEN_REFRESHED event,
|
|
174
|
+
// it calls updateSession() which triggers this callback with the new tokens.
|
|
175
|
+
// We clear any stale error (e.g., TokenExpired) on successful update.
|
|
176
|
+
if (trigger === 'update' && sessionUpdate) {
|
|
177
|
+
const update = sessionUpdate as Record<string, unknown>;
|
|
178
|
+
return {
|
|
179
|
+
...token,
|
|
180
|
+
...(update.accessToken ? { accessToken: update.accessToken } : {}),
|
|
181
|
+
...(update.refreshToken ? { refreshToken: update.refreshToken } : {}),
|
|
182
|
+
...(update.idToken ? { idToken: update.idToken } : {}),
|
|
183
|
+
...(update.accessTokenExpires ? { accessTokenExpires: update.accessTokenExpires } : {}),
|
|
184
|
+
...(update.zkEvm ? { zkEvm: update.zkEvm } : {}),
|
|
185
|
+
// Clear any stale error when valid tokens are synced from client-side
|
|
186
|
+
error: undefined,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Return token if not expired
|
|
191
|
+
if (!isTokenExpired(token.accessTokenExpires as number)) {
|
|
192
|
+
return token;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Token expired - DON'T refresh server-side!
|
|
196
|
+
// Server-side refresh causes race conditions with 403 errors when multiple
|
|
197
|
+
// concurrent SSR requests detect expired tokens. The pendingRefreshes Map
|
|
198
|
+
// mutex doesn't work across serverless isolates/processes.
|
|
199
|
+
//
|
|
200
|
+
// Instead, mark the token as expired and let the client handle refresh:
|
|
201
|
+
// 1. SSR completes with session.error = "TokenExpired"
|
|
202
|
+
// 2. Client hydrates, calls getAccessToken() for data fetches
|
|
203
|
+
// 3. getAccessToken() calls auth.getAccessToken() which auto-refreshes
|
|
204
|
+
// with proper mutex protection (refreshingPromise in @imtbl/auth)
|
|
205
|
+
// 4. TOKEN_REFRESHED event fires → updateSession() syncs fresh tokens
|
|
206
|
+
// 5. NextAuth receives update trigger → clears error, stores fresh tokens
|
|
207
|
+
return {
|
|
208
|
+
...token,
|
|
209
|
+
error: 'TokenExpired',
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
214
|
+
async session({ session, token }: any) {
|
|
215
|
+
// Expose token data to the session
|
|
216
|
+
return {
|
|
217
|
+
...session,
|
|
218
|
+
user: {
|
|
219
|
+
...session.user,
|
|
220
|
+
sub: token.sub as string,
|
|
221
|
+
email: token.email as string | undefined,
|
|
222
|
+
nickname: token.nickname as string | undefined,
|
|
223
|
+
},
|
|
224
|
+
accessToken: token.accessToken as string,
|
|
225
|
+
refreshToken: token.refreshToken as string | undefined,
|
|
226
|
+
idToken: token.idToken as string | undefined,
|
|
227
|
+
accessTokenExpires: token.accessTokenExpires as number,
|
|
228
|
+
zkEvm: token.zkEvm,
|
|
229
|
+
...(token.error && { error: token.error as string }),
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
session: {
|
|
235
|
+
strategy: 'jwt',
|
|
236
|
+
// Session max age in seconds (365 days default)
|
|
237
|
+
maxAge: DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Keep backwards compatibility alias
|
|
243
|
+
export const createAuthOptions = createAuthConfig;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for @imtbl/auth-next-server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default Immutable authentication domain
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_AUTH_DOMAIN = 'https://auth.immutable.com';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default OAuth audience
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_AUDIENCE = 'platform_api';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default OAuth scopes
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_SCOPE = 'openid profile email offline_access transact';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* NextAuth credentials provider ID for Immutable
|
|
22
|
+
*/
|
|
23
|
+
export const IMMUTABLE_PROVIDER_ID = 'immutable';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default NextAuth API base path
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_NEXTAUTH_BASE_PATH = '/api/auth';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default token expiry in seconds (15 minutes)
|
|
32
|
+
* Used as fallback when exp claim cannot be extracted from JWT
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default token expiry in milliseconds
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Buffer time in seconds before token expiry to trigger refresh
|
|
43
|
+
* Tokens will be refreshed when they expire within this window
|
|
44
|
+
*/
|
|
45
|
+
export const TOKEN_EXPIRY_BUFFER_SECONDS = 60;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default session max age in seconds (365 days)
|
|
49
|
+
* This is how long the NextAuth session cookie will be valid
|
|
50
|
+
*/
|
|
51
|
+
export const DEFAULT_SESSION_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;
|