@imtbl/auth-nextjs 0.0.1-alpha.0 → 2.12.4-alpha.5
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/dist/node/chunk-OPPMGNFZ.js +210 -0
- package/dist/node/client/index.cjs +380 -0
- package/dist/node/client/index.js +365 -0
- package/dist/node/index.cjs +296 -0
- package/dist/node/index.js +55 -0
- package/dist/node/server/index.cjs +300 -0
- package/dist/node/server/index.js +57 -0
- package/dist/types/client/callback.d.ts +37 -0
- package/dist/types/client/index.d.ts +5 -0
- package/dist/types/client/provider.d.ts +70 -0
- package/dist/types/config.d.ts +23 -0
- package/dist/types/constants.d.ts +42 -0
- package/dist/types/index.d.ts +63 -0
- package/dist/types/refresh.d.ts +16 -0
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/with-page-auth.d.ts +94 -0
- package/dist/types/types.d.ts +192 -0
- package/dist/types/utils/token.d.ts +8 -0
- package/package.json +2 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthOptions,
|
|
3
|
+
isTokenExpired,
|
|
4
|
+
refreshAccessToken
|
|
5
|
+
} from "./chunk-OPPMGNFZ.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import NextAuthDefault from "next-auth";
|
|
9
|
+
import { MarketingConsentStatus } from "@imtbl/auth";
|
|
10
|
+
var NextAuth = NextAuthDefault.default || NextAuthDefault;
|
|
11
|
+
function ImmutableAuth(config, overrides) {
|
|
12
|
+
const authOptions = createAuthOptions(config);
|
|
13
|
+
if (!overrides) {
|
|
14
|
+
return NextAuth(authOptions);
|
|
15
|
+
}
|
|
16
|
+
const composedCallbacks = {
|
|
17
|
+
...authOptions.callbacks
|
|
18
|
+
};
|
|
19
|
+
if (overrides.callbacks) {
|
|
20
|
+
if (overrides.callbacks.jwt) {
|
|
21
|
+
const internalJwt = authOptions.callbacks?.jwt;
|
|
22
|
+
const userJwt = overrides.callbacks.jwt;
|
|
23
|
+
composedCallbacks.jwt = async (params) => {
|
|
24
|
+
const token = internalJwt ? await internalJwt(params) : params.token;
|
|
25
|
+
return userJwt({ ...params, token });
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (overrides.callbacks.session) {
|
|
29
|
+
const internalSession = authOptions.callbacks?.session;
|
|
30
|
+
const userSession = overrides.callbacks.session;
|
|
31
|
+
composedCallbacks.session = async (params) => {
|
|
32
|
+
const session = internalSession ? await internalSession(params) : params.session;
|
|
33
|
+
return userSession({ ...params, session });
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (overrides.callbacks.signIn) {
|
|
37
|
+
composedCallbacks.signIn = overrides.callbacks.signIn;
|
|
38
|
+
}
|
|
39
|
+
if (overrides.callbacks.redirect) {
|
|
40
|
+
composedCallbacks.redirect = overrides.callbacks.redirect;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const mergedOptions = {
|
|
44
|
+
...authOptions,
|
|
45
|
+
...overrides,
|
|
46
|
+
callbacks: composedCallbacks
|
|
47
|
+
};
|
|
48
|
+
return NextAuth(mergedOptions);
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
ImmutableAuth,
|
|
52
|
+
MarketingConsentStatus,
|
|
53
|
+
isTokenExpired,
|
|
54
|
+
refreshAccessToken
|
|
55
|
+
};
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/server/index.ts
|
|
31
|
+
var server_exports = {};
|
|
32
|
+
__export(server_exports, {
|
|
33
|
+
getImmutableSession: () => getImmutableSession,
|
|
34
|
+
withPageAuthRequired: () => withPageAuthRequired
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(server_exports);
|
|
37
|
+
|
|
38
|
+
// src/server/with-page-auth.ts
|
|
39
|
+
var import_next_auth = require("next-auth");
|
|
40
|
+
|
|
41
|
+
// src/config.ts
|
|
42
|
+
var import_credentials = __toESM(require("next-auth/providers/credentials"), 1);
|
|
43
|
+
|
|
44
|
+
// src/constants.ts
|
|
45
|
+
var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
|
|
46
|
+
var IMMUTABLE_PROVIDER_ID = "immutable";
|
|
47
|
+
var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
48
|
+
var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
|
|
49
|
+
var TOKEN_EXPIRY_BUFFER_SECONDS = 60;
|
|
50
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
51
|
+
|
|
52
|
+
// src/refresh.ts
|
|
53
|
+
async function refreshAccessToken(token, config) {
|
|
54
|
+
const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
|
|
55
|
+
if (!token.refreshToken) {
|
|
56
|
+
return {
|
|
57
|
+
...token,
|
|
58
|
+
error: "NoRefreshToken"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(`${authDomain}/oauth/token`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
66
|
+
},
|
|
67
|
+
body: new URLSearchParams({
|
|
68
|
+
grant_type: "refresh_token",
|
|
69
|
+
client_id: config.clientId,
|
|
70
|
+
refresh_token: token.refreshToken
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
let errorMessage = `Token refresh failed with status ${response.status}`;
|
|
75
|
+
try {
|
|
76
|
+
const errorData = await response.json();
|
|
77
|
+
if (errorData.error_description || errorData.error) {
|
|
78
|
+
errorMessage = errorData.error_description || errorData.error;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
throw new Error(errorMessage);
|
|
83
|
+
}
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
if (!data.access_token || typeof data.access_token !== "string") {
|
|
86
|
+
throw new Error("Invalid token response: missing access_token");
|
|
87
|
+
}
|
|
88
|
+
const expiresIn = data.expires_in || DEFAULT_TOKEN_EXPIRY_SECONDS;
|
|
89
|
+
const accessTokenExpires = Date.now() + expiresIn * 1e3;
|
|
90
|
+
return {
|
|
91
|
+
...token,
|
|
92
|
+
accessToken: data.access_token,
|
|
93
|
+
refreshToken: data.refresh_token ?? token.refreshToken,
|
|
94
|
+
idToken: data.id_token ?? token.idToken,
|
|
95
|
+
accessTokenExpires,
|
|
96
|
+
error: void 0
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error("[auth-nextjs] Failed to refresh token:", error);
|
|
100
|
+
return {
|
|
101
|
+
...token,
|
|
102
|
+
error: "RefreshTokenError"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isTokenExpired(accessTokenExpires, bufferSeconds = TOKEN_EXPIRY_BUFFER_SECONDS) {
|
|
107
|
+
if (typeof accessTokenExpires !== "number" || Number.isNaN(accessTokenExpires)) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return Date.now() >= accessTokenExpires - bufferSeconds * 1e3;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/config.ts
|
|
114
|
+
var CredentialsProvider = import_credentials.default.default || import_credentials.default;
|
|
115
|
+
async function validateTokens(accessToken, authDomain) {
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(`${authDomain}/userinfo`, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
headers: {
|
|
120
|
+
Authorization: `Bearer ${accessToken}`
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
console.error("[auth-nextjs] Token validation failed:", response.status, response.statusText);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return await response.json();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("[auth-nextjs] Token validation error:", error);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function createAuthOptions(config) {
|
|
134
|
+
const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
|
|
135
|
+
return {
|
|
136
|
+
providers: [
|
|
137
|
+
CredentialsProvider({
|
|
138
|
+
id: IMMUTABLE_PROVIDER_ID,
|
|
139
|
+
name: "Immutable",
|
|
140
|
+
credentials: {
|
|
141
|
+
tokens: { label: "Tokens", type: "text" }
|
|
142
|
+
},
|
|
143
|
+
async authorize(credentials) {
|
|
144
|
+
if (!credentials?.tokens) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
let tokenData;
|
|
148
|
+
try {
|
|
149
|
+
tokenData = JSON.parse(credentials.tokens);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("[auth-nextjs] Failed to parse token data:", error);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
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)) {
|
|
155
|
+
console.error("[auth-nextjs] Invalid token data structure - missing required fields");
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const userInfo = await validateTokens(tokenData.accessToken, authDomain);
|
|
159
|
+
if (!userInfo) {
|
|
160
|
+
console.error("[auth-nextjs] Token validation failed - rejecting authentication");
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (userInfo.sub !== tokenData.profile.sub) {
|
|
164
|
+
console.error(
|
|
165
|
+
"[auth-nextjs] User ID mismatch - userinfo sub:",
|
|
166
|
+
userInfo.sub,
|
|
167
|
+
"provided sub:",
|
|
168
|
+
tokenData.profile.sub
|
|
169
|
+
);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
id: userInfo.sub,
|
|
174
|
+
sub: userInfo.sub,
|
|
175
|
+
email: userInfo.email ?? tokenData.profile.email,
|
|
176
|
+
nickname: userInfo.nickname ?? tokenData.profile.nickname,
|
|
177
|
+
accessToken: tokenData.accessToken,
|
|
178
|
+
refreshToken: tokenData.refreshToken,
|
|
179
|
+
idToken: tokenData.idToken,
|
|
180
|
+
accessTokenExpires: tokenData.accessTokenExpires,
|
|
181
|
+
zkEvm: tokenData.zkEvm
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
],
|
|
186
|
+
callbacks: {
|
|
187
|
+
async jwt({
|
|
188
|
+
token,
|
|
189
|
+
user,
|
|
190
|
+
trigger,
|
|
191
|
+
session: sessionUpdate
|
|
192
|
+
}) {
|
|
193
|
+
if (user) {
|
|
194
|
+
return {
|
|
195
|
+
...token,
|
|
196
|
+
sub: user.sub,
|
|
197
|
+
email: user.email,
|
|
198
|
+
nickname: user.nickname,
|
|
199
|
+
accessToken: user.accessToken,
|
|
200
|
+
refreshToken: user.refreshToken,
|
|
201
|
+
idToken: user.idToken,
|
|
202
|
+
accessTokenExpires: user.accessTokenExpires,
|
|
203
|
+
zkEvm: user.zkEvm
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (trigger === "update" && sessionUpdate) {
|
|
207
|
+
return {
|
|
208
|
+
...token,
|
|
209
|
+
...sessionUpdate.accessToken && { accessToken: sessionUpdate.accessToken },
|
|
210
|
+
...sessionUpdate.refreshToken && { refreshToken: sessionUpdate.refreshToken },
|
|
211
|
+
...sessionUpdate.idToken && { idToken: sessionUpdate.idToken },
|
|
212
|
+
...sessionUpdate.accessTokenExpires && { accessTokenExpires: sessionUpdate.accessTokenExpires },
|
|
213
|
+
...sessionUpdate.zkEvm && { zkEvm: sessionUpdate.zkEvm }
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!isTokenExpired(token.accessTokenExpires)) {
|
|
217
|
+
return token;
|
|
218
|
+
}
|
|
219
|
+
return refreshAccessToken(token, config);
|
|
220
|
+
},
|
|
221
|
+
async session({ session, token }) {
|
|
222
|
+
return {
|
|
223
|
+
...session,
|
|
224
|
+
user: {
|
|
225
|
+
sub: token.sub,
|
|
226
|
+
email: token.email,
|
|
227
|
+
nickname: token.nickname
|
|
228
|
+
},
|
|
229
|
+
accessToken: token.accessToken,
|
|
230
|
+
refreshToken: token.refreshToken,
|
|
231
|
+
idToken: token.idToken,
|
|
232
|
+
accessTokenExpires: token.accessTokenExpires,
|
|
233
|
+
zkEvm: token.zkEvm,
|
|
234
|
+
...token.error && { error: token.error }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
session: {
|
|
239
|
+
strategy: "jwt",
|
|
240
|
+
// Session max age in seconds (30 days default)
|
|
241
|
+
maxAge: DEFAULT_SESSION_MAX_AGE_SECONDS
|
|
242
|
+
},
|
|
243
|
+
// Use NEXTAUTH_SECRET from environment
|
|
244
|
+
secret: process.env.NEXTAUTH_SECRET
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/server/with-page-auth.ts
|
|
249
|
+
async function getImmutableSession(req, res, config) {
|
|
250
|
+
const authOptions = createAuthOptions(config);
|
|
251
|
+
return (0, import_next_auth.getServerSession)(req, res, authOptions);
|
|
252
|
+
}
|
|
253
|
+
function withPageAuthRequired(config, options = {}) {
|
|
254
|
+
const {
|
|
255
|
+
loginUrl = "/login",
|
|
256
|
+
returnTo,
|
|
257
|
+
getServerSideProps: customGetServerSideProps
|
|
258
|
+
} = options;
|
|
259
|
+
const authOptions = createAuthOptions(config);
|
|
260
|
+
return async (ctx) => {
|
|
261
|
+
const session = await (0, import_next_auth.getServerSession)(ctx.req, ctx.res, authOptions);
|
|
262
|
+
if (!session) {
|
|
263
|
+
let destination = loginUrl;
|
|
264
|
+
if (returnTo !== false) {
|
|
265
|
+
const returnPath = returnTo || ctx.resolvedUrl;
|
|
266
|
+
const separator = loginUrl.includes("?") ? "&" : "?";
|
|
267
|
+
destination = `${loginUrl}${separator}returnTo=${encodeURIComponent(returnPath)}`;
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
redirect: {
|
|
271
|
+
destination,
|
|
272
|
+
permanent: false
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (customGetServerSideProps) {
|
|
277
|
+
const result = await customGetServerSideProps(ctx, session);
|
|
278
|
+
if ("redirect" in result || "notFound" in result) {
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
const userProps = await result.props;
|
|
282
|
+
return {
|
|
283
|
+
props: {
|
|
284
|
+
...userProps,
|
|
285
|
+
session
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
props: {
|
|
291
|
+
session
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
297
|
+
0 && (module.exports = {
|
|
298
|
+
getImmutableSession,
|
|
299
|
+
withPageAuthRequired
|
|
300
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthOptions
|
|
3
|
+
} from "../chunk-OPPMGNFZ.js";
|
|
4
|
+
|
|
5
|
+
// src/server/with-page-auth.ts
|
|
6
|
+
import { getServerSession as nextAuthGetServerSession } from "next-auth";
|
|
7
|
+
async function getImmutableSession(req, res, config) {
|
|
8
|
+
const authOptions = createAuthOptions(config);
|
|
9
|
+
return nextAuthGetServerSession(req, res, authOptions);
|
|
10
|
+
}
|
|
11
|
+
function withPageAuthRequired(config, options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
loginUrl = "/login",
|
|
14
|
+
returnTo,
|
|
15
|
+
getServerSideProps: customGetServerSideProps
|
|
16
|
+
} = options;
|
|
17
|
+
const authOptions = createAuthOptions(config);
|
|
18
|
+
return async (ctx) => {
|
|
19
|
+
const session = await nextAuthGetServerSession(ctx.req, ctx.res, authOptions);
|
|
20
|
+
if (!session) {
|
|
21
|
+
let destination = loginUrl;
|
|
22
|
+
if (returnTo !== false) {
|
|
23
|
+
const returnPath = returnTo || ctx.resolvedUrl;
|
|
24
|
+
const separator = loginUrl.includes("?") ? "&" : "?";
|
|
25
|
+
destination = `${loginUrl}${separator}returnTo=${encodeURIComponent(returnPath)}`;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
redirect: {
|
|
29
|
+
destination,
|
|
30
|
+
permanent: false
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (customGetServerSideProps) {
|
|
35
|
+
const result = await customGetServerSideProps(ctx, session);
|
|
36
|
+
if ("redirect" in result || "notFound" in result) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
const userProps = await result.props;
|
|
40
|
+
return {
|
|
41
|
+
props: {
|
|
42
|
+
...userProps,
|
|
43
|
+
session
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
props: {
|
|
49
|
+
session
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
getImmutableSession,
|
|
56
|
+
withPageAuthRequired
|
|
57
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ImmutableAuthConfig } from '../types';
|
|
3
|
+
export interface CallbackPageProps {
|
|
4
|
+
/**
|
|
5
|
+
* Immutable auth configuration
|
|
6
|
+
*/
|
|
7
|
+
config: ImmutableAuthConfig;
|
|
8
|
+
/**
|
|
9
|
+
* URL to redirect to after successful authentication (when not in popup)
|
|
10
|
+
*/
|
|
11
|
+
redirectTo?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom loading component
|
|
14
|
+
*/
|
|
15
|
+
loadingComponent?: React.ReactElement | null;
|
|
16
|
+
/**
|
|
17
|
+
* Custom error component
|
|
18
|
+
*/
|
|
19
|
+
errorComponent?: (error: string) => React.ReactElement | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Callback page component for handling OAuth redirects.
|
|
23
|
+
*
|
|
24
|
+
* Use this in your callback page to process authentication responses.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* // pages/callback.tsx
|
|
29
|
+
* import { CallbackPage } from "@imtbl/auth-nextjs/client";
|
|
30
|
+
* import { immutableConfig } from "@/lib/auth-nextjs";
|
|
31
|
+
*
|
|
32
|
+
* export default function Callback() {
|
|
33
|
+
* return <CallbackPage config={immutableConfig} />;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function CallbackPage({ config, redirectTo, loadingComponent, errorComponent, }: CallbackPageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ImmutableAuthProvider, useImmutableAuth, useAccessToken, } from './provider';
|
|
2
|
+
export { CallbackPage, type CallbackPageProps } from './callback';
|
|
3
|
+
export type { ImmutableAuthProviderProps, UseImmutableAuthReturn, ImmutableAuthConfig, ImmutableUser, } from '../types';
|
|
4
|
+
export type { LoginOptions, DirectLoginOptions } from '@imtbl/auth';
|
|
5
|
+
export { MarketingConsentStatus } from '@imtbl/auth';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ImmutableAuthProviderProps, UseImmutableAuthReturn } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Provider component for Immutable authentication with NextAuth
|
|
4
|
+
*
|
|
5
|
+
* Wraps your app to provide authentication state via useImmutableAuth hook.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // pages/_app.tsx
|
|
10
|
+
* import { ImmutableAuthProvider } from "@imtbl/auth-nextjs/client";
|
|
11
|
+
*
|
|
12
|
+
* const config = {
|
|
13
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
14
|
+
* redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* export default function App({ Component, pageProps }: AppProps) {
|
|
18
|
+
* return (
|
|
19
|
+
* <ImmutableAuthProvider config={config} session={pageProps.session}>
|
|
20
|
+
* <Component {...pageProps} />
|
|
21
|
+
* </ImmutableAuthProvider>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function ImmutableAuthProvider({ children, config, session, basePath, }: ImmutableAuthProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
/**
|
|
28
|
+
* Hook to access Immutable authentication state and methods
|
|
29
|
+
*
|
|
30
|
+
* Must be used within an ImmutableAuthProvider.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function MyComponent() {
|
|
35
|
+
* const { user, isLoading, signIn, signOut } = useImmutableAuth();
|
|
36
|
+
*
|
|
37
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
38
|
+
*
|
|
39
|
+
* if (user) {
|
|
40
|
+
* return (
|
|
41
|
+
* <div>
|
|
42
|
+
* <p>Welcome, {user.email}</p>
|
|
43
|
+
* <button onClick={signOut}>Logout</button>
|
|
44
|
+
* </div>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* return <button onClick={signIn}>Login</button>;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function useImmutableAuth(): UseImmutableAuthReturn;
|
|
53
|
+
/**
|
|
54
|
+
* Hook to get a function that returns a valid access token
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* function ApiComponent() {
|
|
59
|
+
* const getAccessToken = useAccessToken();
|
|
60
|
+
*
|
|
61
|
+
* const fetchData = async () => {
|
|
62
|
+
* const token = await getAccessToken();
|
|
63
|
+
* const response = await fetch("/api/data", {
|
|
64
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
65
|
+
* });
|
|
66
|
+
* };
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function useAccessToken(): () => Promise<string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NextAuthOptions } from 'next-auth';
|
|
2
|
+
import type { ImmutableAuthConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Create NextAuth options configured for Immutable authentication
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // lib/auth.ts
|
|
9
|
+
* import { createAuthOptions } from "@imtbl/auth-nextjs";
|
|
10
|
+
*
|
|
11
|
+
* export const authOptions = createAuthOptions({
|
|
12
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
13
|
+
* redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // pages/api/auth/[...nextauth].ts
|
|
17
|
+
* import NextAuth from "next-auth";
|
|
18
|
+
* import { authOptions } from "@/lib/auth";
|
|
19
|
+
*
|
|
20
|
+
* export default NextAuth(authOptions);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function createAuthOptions(config: ImmutableAuthConfig): NextAuthOptions;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for @imtbl/auth-nextjs
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Default Immutable authentication domain
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
|
|
8
|
+
/**
|
|
9
|
+
* Default OAuth audience
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_AUDIENCE = "platform_api";
|
|
12
|
+
/**
|
|
13
|
+
* Default OAuth scopes
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_SCOPE = "openid profile email offline_access transact";
|
|
16
|
+
/**
|
|
17
|
+
* NextAuth credentials provider ID for Immutable
|
|
18
|
+
*/
|
|
19
|
+
export declare const IMMUTABLE_PROVIDER_ID = "immutable";
|
|
20
|
+
/**
|
|
21
|
+
* Default NextAuth API base path
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_NEXTAUTH_BASE_PATH = "/api/auth";
|
|
24
|
+
/**
|
|
25
|
+
* Default token expiry in seconds (15 minutes)
|
|
26
|
+
* Used as fallback when exp claim cannot be extracted from JWT
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
29
|
+
/**
|
|
30
|
+
* Default token expiry in milliseconds
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_TOKEN_EXPIRY_MS: number;
|
|
33
|
+
/**
|
|
34
|
+
* Buffer time in seconds before token expiry to trigger refresh
|
|
35
|
+
* Tokens will be refreshed when they expire within this window
|
|
36
|
+
*/
|
|
37
|
+
export declare const TOKEN_EXPIRY_BUFFER_SECONDS = 60;
|
|
38
|
+
/**
|
|
39
|
+
* Default session max age in seconds (30 days)
|
|
40
|
+
* This is how long the NextAuth session cookie will be valid
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_SESSION_MAX_AGE_SECONDS: number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type NextAuthOptions } from 'next-auth';
|
|
2
|
+
import type { ImmutableAuthConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* NextAuth options that can be overridden.
|
|
5
|
+
* Excludes 'providers' as that's managed internally.
|
|
6
|
+
*/
|
|
7
|
+
export type ImmutableAuthOverrides = Omit<NextAuthOptions, 'providers'>;
|
|
8
|
+
/**
|
|
9
|
+
* Create a NextAuth handler with Immutable authentication
|
|
10
|
+
*
|
|
11
|
+
* @param config - Immutable auth configuration
|
|
12
|
+
* @param overrides - Optional NextAuth options to override defaults
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Callback composition: The `jwt` and `session` callbacks are composed rather than
|
|
16
|
+
* replaced. Internal callbacks run first (handling token storage and refresh), then
|
|
17
|
+
* your custom callbacks receive the result. Other callbacks (`signIn`, `redirect`)
|
|
18
|
+
* are replaced entirely if provided.
|
|
19
|
+
*
|
|
20
|
+
* @example Basic usage
|
|
21
|
+
* ```typescript
|
|
22
|
+
* // pages/api/auth/[...nextauth].ts
|
|
23
|
+
* import { ImmutableAuth } from "@imtbl/auth-nextjs";
|
|
24
|
+
*
|
|
25
|
+
* export default ImmutableAuth({
|
|
26
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
27
|
+
* redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example With NextAuth overrides
|
|
32
|
+
* ```typescript
|
|
33
|
+
* export default ImmutableAuth(
|
|
34
|
+
* { clientId: "...", redirectUri: "..." },
|
|
35
|
+
* {
|
|
36
|
+
* pages: { signIn: "/custom-login", error: "/auth-error" },
|
|
37
|
+
* debug: true,
|
|
38
|
+
* }
|
|
39
|
+
* );
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example With custom jwt callback (composed with internal callback)
|
|
43
|
+
* ```typescript
|
|
44
|
+
* export default ImmutableAuth(
|
|
45
|
+
* { clientId: "...", redirectUri: "..." },
|
|
46
|
+
* {
|
|
47
|
+
* callbacks: {
|
|
48
|
+
* // Your jwt callback receives the token after internal processing
|
|
49
|
+
* async jwt({ token }) {
|
|
50
|
+
* // Add custom claims
|
|
51
|
+
* token.customClaim = "value";
|
|
52
|
+
* return token;
|
|
53
|
+
* },
|
|
54
|
+
* },
|
|
55
|
+
* }
|
|
56
|
+
* );
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function ImmutableAuth(config: ImmutableAuthConfig, overrides?: ImmutableAuthOverrides): any;
|
|
60
|
+
export type { ImmutableAuthConfig, ImmutableTokenData, ImmutableUser, ZkEvmInfo, WithPageAuthRequiredOptions, } from './types';
|
|
61
|
+
export type { LoginOptions, DirectLoginOptions } from '@imtbl/auth';
|
|
62
|
+
export { MarketingConsentStatus } from '@imtbl/auth';
|
|
63
|
+
export { refreshAccessToken, isTokenExpired } from './refresh';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JWT } from 'next-auth/jwt';
|
|
2
|
+
import type { ImmutableAuthConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Refresh the access token using the refresh token
|
|
5
|
+
* Called by NextAuth JWT callback when token is expired
|
|
6
|
+
*/
|
|
7
|
+
export declare function refreshAccessToken(token: JWT, config: ImmutableAuthConfig): Promise<JWT>;
|
|
8
|
+
/**
|
|
9
|
+
* Check if the access token is expired or about to expire
|
|
10
|
+
* Returns true if token expires within the buffer time (default 60 seconds)
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* If accessTokenExpires is not a valid number (undefined, null, NaN),
|
|
14
|
+
* returns true to trigger a refresh as a safety measure.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isTokenExpired(accessTokenExpires: number, bufferSeconds?: number): boolean;
|