@caffeinebounce/identity 0.12.1 → 0.12.3
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/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +203 -124
- package/dist/index.mjs +205 -126
- package/dist/server.js +54 -17
- package/dist/server.mjs +54 -17
- package/package.json +2 -2
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
export { generateSecureToken, getClientIP, getGeolocationFromIP, hashString } from '@caffeinebounce/shared-utils';
|
|
3
3
|
|
|
4
|
+
// src/handlers/callback.ts
|
|
5
|
+
|
|
6
|
+
// src/utils/redirect.ts
|
|
7
|
+
var INTERNAL_REDIRECT_BASE = new URL("https://identity.invalid");
|
|
8
|
+
var ENCODED_PATH_SEPARATOR = /%(?:2f|5c)/i;
|
|
9
|
+
function hasControlCharacter(value) {
|
|
10
|
+
return Array.from(value).some((character) => {
|
|
11
|
+
const code = character.charCodeAt(0);
|
|
12
|
+
return code <= 31 || code === 127;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function isStrictInternalRedirect(candidate) {
|
|
16
|
+
if (!candidate?.startsWith("/") || hasControlCharacter(candidate)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const pathEnd = candidate.search(/[?#]/);
|
|
20
|
+
const pathname = pathEnd === -1 ? candidate : candidate.slice(0, pathEnd);
|
|
21
|
+
if (pathname.includes("\\") || ENCODED_PATH_SEPARATOR.test(pathname)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const target = new URL(candidate, INTERNAL_REDIRECT_BASE);
|
|
26
|
+
return target.origin === INTERNAL_REDIRECT_BASE.origin;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function getSafeInternalRedirect(candidate, fallback) {
|
|
32
|
+
if (isStrictInternalRedirect(candidate)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
if (fallback === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return isStrictInternalRedirect(fallback) ? fallback : "/";
|
|
39
|
+
}
|
|
40
|
+
|
|
4
41
|
// src/handlers/callback.ts
|
|
5
42
|
var POST_AUTH_HOOK_ERROR_MESSAGE = "Authentication completed, but setup failed. Please try again.";
|
|
6
43
|
var DEFAULT_LINKING_ACCOUNT_ERROR_MESSAGE = "This account is already connected to another account. Each external account can only be linked to one account.";
|
|
@@ -12,12 +49,6 @@ var EMAIL_OTP_TYPES = [
|
|
|
12
49
|
"email_change",
|
|
13
50
|
"email"
|
|
14
51
|
];
|
|
15
|
-
function getSafeRedirectPath(candidate, fallback) {
|
|
16
|
-
if (candidate?.startsWith("/") && !candidate.startsWith("//")) {
|
|
17
|
-
return candidate;
|
|
18
|
-
}
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
52
|
function getEmailOtpType(value) {
|
|
22
53
|
if (!value) {
|
|
23
54
|
return null;
|
|
@@ -108,8 +139,9 @@ function createRedirectResponse(target, origin) {
|
|
|
108
139
|
if (target instanceof URL) {
|
|
109
140
|
return NextResponse.redirect(target.toString());
|
|
110
141
|
}
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
const safeTarget = getSafeInternalRedirect(target, null);
|
|
143
|
+
if (safeTarget) {
|
|
144
|
+
return NextResponse.redirect(`${origin}${safeTarget}`);
|
|
113
145
|
}
|
|
114
146
|
return null;
|
|
115
147
|
}
|
|
@@ -171,15 +203,20 @@ function createAuthCallbackHandler({
|
|
|
171
203
|
isLinkingFlow = isDefaultLinkingFlow,
|
|
172
204
|
resolveLinkingErrorMessage
|
|
173
205
|
}) {
|
|
206
|
+
const safeDefaultRedirect = getSafeInternalRedirect(
|
|
207
|
+
defaultRedirect,
|
|
208
|
+
"/dashboard"
|
|
209
|
+
);
|
|
210
|
+
const safeSignInPath = getSafeInternalRedirect(signInPath, "/signin");
|
|
174
211
|
return async function GET(request) {
|
|
175
212
|
const requestUrl = new URL(request.url);
|
|
176
213
|
const code = requestUrl.searchParams.get("code");
|
|
177
214
|
const tokenHash = requestUrl.searchParams.get("token_hash");
|
|
178
215
|
const rawOtpType = requestUrl.searchParams.get("type");
|
|
179
216
|
const otpType = getEmailOtpType(rawOtpType);
|
|
180
|
-
const next =
|
|
217
|
+
const next = getSafeInternalRedirect(
|
|
181
218
|
requestUrl.searchParams.get("next") ?? requestUrl.searchParams.get("redirect_to"),
|
|
182
|
-
|
|
219
|
+
safeDefaultRedirect
|
|
183
220
|
);
|
|
184
221
|
const origin = requestUrl.origin;
|
|
185
222
|
const error = requestUrl.searchParams.get("error");
|
|
@@ -201,7 +238,7 @@ function createAuthCallbackHandler({
|
|
|
201
238
|
return linkingErrorRedirect;
|
|
202
239
|
}
|
|
203
240
|
return NextResponse.redirect(
|
|
204
|
-
`${origin}${
|
|
241
|
+
`${origin}${safeSignInPath}?error=${encodeURIComponent(message)}`
|
|
205
242
|
);
|
|
206
243
|
}
|
|
207
244
|
if (code) {
|
|
@@ -216,7 +253,7 @@ function createAuthCallbackHandler({
|
|
|
216
253
|
flow: "oauth",
|
|
217
254
|
postAuthHook,
|
|
218
255
|
postAuthHookErrorMode,
|
|
219
|
-
signInPath,
|
|
256
|
+
signInPath: safeSignInPath,
|
|
220
257
|
resolveSuccessRedirect
|
|
221
258
|
});
|
|
222
259
|
}
|
|
@@ -236,13 +273,13 @@ function createAuthCallbackHandler({
|
|
|
236
273
|
return linkingErrorRedirect;
|
|
237
274
|
}
|
|
238
275
|
return NextResponse.redirect(
|
|
239
|
-
`${origin}${
|
|
276
|
+
`${origin}${safeSignInPath}?error=${encodeURIComponent(errorMessage)}`
|
|
240
277
|
);
|
|
241
278
|
}
|
|
242
279
|
if (tokenHash || rawOtpType) {
|
|
243
280
|
if (!tokenHash || !otpType) {
|
|
244
281
|
return NextResponse.redirect(
|
|
245
|
-
`${origin}${
|
|
282
|
+
`${origin}${safeSignInPath}?error=${encodeURIComponent("Invalid verification link")}`
|
|
246
283
|
);
|
|
247
284
|
}
|
|
248
285
|
const supabase = await createClient();
|
|
@@ -260,16 +297,16 @@ function createAuthCallbackHandler({
|
|
|
260
297
|
otpType,
|
|
261
298
|
postAuthHook,
|
|
262
299
|
postAuthHookErrorMode,
|
|
263
|
-
signInPath,
|
|
300
|
+
signInPath: safeSignInPath,
|
|
264
301
|
resolveSuccessRedirect
|
|
265
302
|
});
|
|
266
303
|
}
|
|
267
304
|
return NextResponse.redirect(
|
|
268
|
-
`${origin}${
|
|
305
|
+
`${origin}${safeSignInPath}?error=${encodeURIComponent(verifyError.message)}`
|
|
269
306
|
);
|
|
270
307
|
}
|
|
271
308
|
return NextResponse.redirect(
|
|
272
|
-
`${origin}${
|
|
309
|
+
`${origin}${safeSignInPath}?error=No authorization code received`
|
|
273
310
|
);
|
|
274
311
|
};
|
|
275
312
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caffeinebounce/identity",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Authentication components and handlers for Caffeine Bounce projects",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@caffeinebounce/logger": "^0.10.0",
|
|
63
63
|
"@caffeinebounce/shared-utils": "^0.7.136",
|
|
64
|
-
"@caffeinebounce/ui": "^0.62.
|
|
64
|
+
"@caffeinebounce/ui": "^0.62.3",
|
|
65
65
|
"@supabase/ssr": "^0.8.0",
|
|
66
66
|
"@supabase/supabase-js": "^2.49.4",
|
|
67
67
|
"input-otp": "^1.4.2",
|