@becklyn/deployment-protection 0.0.1 → 0.1.0

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 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/handler.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA8B,2BAA2B,EAAE,MAAM,SAAS,CAAC;AA2OvF;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqD1B"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/handler.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA8B,2BAA2B,EAAE,MAAM,SAAS,CAAC;AA6OvF;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqD1B"}
@@ -25,11 +25,13 @@ function htmlResponse(html, status = 401) {
25
25
  },
26
26
  });
27
27
  }
28
- function redirect(location, status = 302) {
28
+ function redirect(request, location, status = 302) {
29
+ // Next.js middleware/proxy requires an absolute Location; relative values throw ERR_INVALID_URL.
30
+ const absoluteLocation = new URL(location, request.url).toString();
29
31
  return new Response(null, {
30
32
  status,
31
33
  headers: {
32
- Location: location,
34
+ Location: absoluteLocation,
33
35
  "Cache-Control": "no-store",
34
36
  },
35
37
  });
@@ -58,7 +60,7 @@ async function handleBypass(request, config) {
58
60
  if (hadQueryBypass || hadSetCookieQuery || setCookieMode) {
59
61
  url.searchParams.delete(constants_1.BYPASS_HEADER);
60
62
  url.searchParams.delete("x-vercel-set-bypass-cookie");
61
- const response = redirect(url.pathname + url.search + url.hash);
63
+ const response = redirect(request, url.pathname + url.search + url.hash);
62
64
  const secure = isSecureRequest(request);
63
65
  if (setCookieMode || hadSetCookieQuery) {
64
66
  (0, vercel_oauth_1.appendSetCookie)(response, constants_1.BYPASS_COOKIE_NAME, config.bypassSecret, {
@@ -104,7 +106,7 @@ async function handlePasswordPost(request, config) {
104
106
  showVercel: (0, config_1.hasVercelAuth)(config),
105
107
  }));
106
108
  }
107
- const response = redirect(returnTo);
109
+ const response = redirect(request, returnTo);
108
110
  return attachSession(response, config, "password", username, isSecureRequest(request));
109
111
  }
110
112
  async function handleVercelAuthorize(request, config) {
@@ -152,7 +154,7 @@ async function handleVercelCallback(request, config) {
152
154
  }
153
155
  const user = await (0, vercel_oauth_1.fetchVercelUserInfo)(tokenData.access_token);
154
156
  const subject = user.preferred_username || user.email || user.sub || "vercel-user";
155
- const response = redirect(returnTo);
157
+ const response = redirect(request, returnTo);
156
158
  (0, vercel_oauth_1.clearOAuthCookies)(response, secure);
157
159
  return attachSession(response, config, "vercel", subject, secure);
158
160
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,KAAK,cAAc,GAAG,CAClB,OAAO,EAAE,WAAW,EACpB,KAAK,CAAC,EAAE,OAAO,KAEb,QAAQ,GACR,YAAY,GACZ,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,GACnD,SAAS,GACT,IAAI,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAClC,EAAE,GACF,CAAC,2BAA2B,CAAC,GAC7B,CAAC,cAAc,CAAC,GAChB,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;AAWpD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CAmBtC;AAED,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,KAAK,cAAc,GAAG,CAClB,OAAO,EAAE,WAAW,EACpB,KAAK,CAAC,EAAE,OAAO,KAEb,QAAQ,GACR,YAAY,GACZ,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,GACnD,SAAS,GACT,IAAI,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAClC,EAAE,GACF,CAAC,2BAA2B,CAAC,GAC7B,CAAC,cAAc,CAAC,GAChB,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;AA8DpD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CActC;AAED,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
@@ -12,6 +12,43 @@ function isOptions(value) {
12
12
  !Array.isArray(value) &&
13
13
  typeof value !== "function");
14
14
  }
15
+ function toNextResponse(request, response) {
16
+ const location = response.headers.get("Location");
17
+ if (location && response.status >= 300 && response.status < 400) {
18
+ // Always resolve against the incoming request so relative Locations never reach Next.
19
+ const redirectResponse = server_1.NextResponse.redirect(new URL(location, request.url), response.status);
20
+ copyHeaders(response, redirectResponse, /* skipLocation */ true);
21
+ return redirectResponse;
22
+ }
23
+ const nextResponse = new server_1.NextResponse(response.body, {
24
+ status: response.status,
25
+ statusText: response.statusText,
26
+ });
27
+ copyHeaders(response, nextResponse, false);
28
+ return nextResponse;
29
+ }
30
+ function copyHeaders(from, to, skipLocation) {
31
+ const setCookies = typeof from.headers.getSetCookie === "function" ? from.headers.getSetCookie() : [];
32
+ if (setCookies.length > 0) {
33
+ for (const cookie of setCookies) {
34
+ to.headers.append("Set-Cookie", cookie);
35
+ }
36
+ }
37
+ from.headers.forEach((value, key) => {
38
+ const lower = key.toLowerCase();
39
+ if (lower === "set-cookie") {
40
+ // Already copied via getSetCookie when available; fall back otherwise.
41
+ if (setCookies.length === 0) {
42
+ to.headers.append("Set-Cookie", value);
43
+ }
44
+ return;
45
+ }
46
+ if (skipLocation && lower === "location") {
47
+ return;
48
+ }
49
+ to.headers.set(key, value);
50
+ });
51
+ }
15
52
  /**
16
53
  * Next.js middleware / proxy wrapper.
17
54
  *
@@ -47,12 +84,7 @@ function withDeploymentProtection(...args) {
47
84
  return async function deploymentProtectionMiddleware(request, event) {
48
85
  const protectionResponse = await (0, handler_1.handleDeploymentProtection)(request, options);
49
86
  if (protectionResponse) {
50
- // Preserve NextResponse subclass when possible
51
- return new server_1.NextResponse(protectionResponse.body, {
52
- status: protectionResponse.status,
53
- statusText: protectionResponse.statusText,
54
- headers: protectionResponse.headers,
55
- });
87
+ return toNextResponse(request, protectionResponse);
56
88
  }
57
89
  if (next) {
58
90
  const result = await next(request, event);
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/handler.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA8B,2BAA2B,EAAE,MAAM,SAAS,CAAC;AA2OvF;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqD1B"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/handler.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA8B,2BAA2B,EAAE,MAAM,SAAS,CAAC;AA6OvF;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqD1B"}
@@ -22,11 +22,13 @@ function htmlResponse(html, status = 401) {
22
22
  },
23
23
  });
24
24
  }
25
- function redirect(location, status = 302) {
25
+ function redirect(request, location, status = 302) {
26
+ // Next.js middleware/proxy requires an absolute Location; relative values throw ERR_INVALID_URL.
27
+ const absoluteLocation = new URL(location, request.url).toString();
26
28
  return new Response(null, {
27
29
  status,
28
30
  headers: {
29
- Location: location,
31
+ Location: absoluteLocation,
30
32
  "Cache-Control": "no-store",
31
33
  },
32
34
  });
@@ -55,7 +57,7 @@ async function handleBypass(request, config) {
55
57
  if (hadQueryBypass || hadSetCookieQuery || setCookieMode) {
56
58
  url.searchParams.delete(BYPASS_HEADER);
57
59
  url.searchParams.delete("x-vercel-set-bypass-cookie");
58
- const response = redirect(url.pathname + url.search + url.hash);
60
+ const response = redirect(request, url.pathname + url.search + url.hash);
59
61
  const secure = isSecureRequest(request);
60
62
  if (setCookieMode || hadSetCookieQuery) {
61
63
  appendSetCookie(response, BYPASS_COOKIE_NAME, config.bypassSecret, {
@@ -101,7 +103,7 @@ async function handlePasswordPost(request, config) {
101
103
  showVercel: hasVercelAuth(config),
102
104
  }));
103
105
  }
104
- const response = redirect(returnTo);
106
+ const response = redirect(request, returnTo);
105
107
  return attachSession(response, config, "password", username, isSecureRequest(request));
106
108
  }
107
109
  async function handleVercelAuthorize(request, config) {
@@ -149,7 +151,7 @@ async function handleVercelCallback(request, config) {
149
151
  }
150
152
  const user = await fetchVercelUserInfo(tokenData.access_token);
151
153
  const subject = user.preferred_username || user.email || user.sub || "vercel-user";
152
- const response = redirect(returnTo);
154
+ const response = redirect(request, returnTo);
153
155
  clearOAuthCookies(response, secure);
154
156
  return attachSession(response, config, "vercel", subject, secure);
155
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,KAAK,cAAc,GAAG,CAClB,OAAO,EAAE,WAAW,EACpB,KAAK,CAAC,EAAE,OAAO,KAEb,QAAQ,GACR,YAAY,GACZ,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,GACnD,SAAS,GACT,IAAI,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAClC,EAAE,GACF,CAAC,2BAA2B,CAAC,GAC7B,CAAC,cAAc,CAAC,GAChB,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;AAWpD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CAmBtC;AAED,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,KAAK,cAAc,GAAG,CAClB,OAAO,EAAE,WAAW,EACpB,KAAK,CAAC,EAAE,OAAO,KAEb,QAAQ,GACR,YAAY,GACZ,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,GACnD,SAAS,GACT,IAAI,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAClC,EAAE,GACF,CAAC,2BAA2B,CAAC,GAC7B,CAAC,cAAc,CAAC,GAChB,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;AA8DpD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CActC;AAED,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
@@ -7,6 +7,43 @@ function isOptions(value) {
7
7
  !Array.isArray(value) &&
8
8
  typeof value !== "function");
9
9
  }
10
+ function toNextResponse(request, response) {
11
+ const location = response.headers.get("Location");
12
+ if (location && response.status >= 300 && response.status < 400) {
13
+ // Always resolve against the incoming request so relative Locations never reach Next.
14
+ const redirectResponse = NextResponse.redirect(new URL(location, request.url), response.status);
15
+ copyHeaders(response, redirectResponse, /* skipLocation */ true);
16
+ return redirectResponse;
17
+ }
18
+ const nextResponse = new NextResponse(response.body, {
19
+ status: response.status,
20
+ statusText: response.statusText,
21
+ });
22
+ copyHeaders(response, nextResponse, false);
23
+ return nextResponse;
24
+ }
25
+ function copyHeaders(from, to, skipLocation) {
26
+ const setCookies = typeof from.headers.getSetCookie === "function" ? from.headers.getSetCookie() : [];
27
+ if (setCookies.length > 0) {
28
+ for (const cookie of setCookies) {
29
+ to.headers.append("Set-Cookie", cookie);
30
+ }
31
+ }
32
+ from.headers.forEach((value, key) => {
33
+ const lower = key.toLowerCase();
34
+ if (lower === "set-cookie") {
35
+ // Already copied via getSetCookie when available; fall back otherwise.
36
+ if (setCookies.length === 0) {
37
+ to.headers.append("Set-Cookie", value);
38
+ }
39
+ return;
40
+ }
41
+ if (skipLocation && lower === "location") {
42
+ return;
43
+ }
44
+ to.headers.set(key, value);
45
+ });
46
+ }
10
47
  /**
11
48
  * Next.js middleware / proxy wrapper.
12
49
  *
@@ -42,12 +79,7 @@ export function withDeploymentProtection(...args) {
42
79
  return async function deploymentProtectionMiddleware(request, event) {
43
80
  const protectionResponse = await handleDeploymentProtection(request, options);
44
81
  if (protectionResponse) {
45
- // Preserve NextResponse subclass when possible
46
- return new NextResponse(protectionResponse.body, {
47
- status: protectionResponse.status,
48
- statusText: protectionResponse.statusText,
49
- headers: protectionResponse.headers,
50
- });
82
+ return toNextResponse(request, protectionResponse);
51
83
  }
52
84
  if (next) {
53
85
  const result = await next(request, event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@becklyn/deployment-protection",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "license": "MIT",
5
5
  "description": "Simple shared username/password + Sign in with Vercel gate for Next.js deployments",
6
6
  "homepage": "https://github.com/Becklyn-Studios/ts-libs/tree/main/packages/deployment-protection",
@@ -18,10 +18,10 @@
18
18
  "devDependencies": {
19
19
  "@becklyn/eslint": "*",
20
20
  "@becklyn/tsconfig": "*",
21
- "@types/node": "^26.2.0",
22
- "next": "^16.3.1",
21
+ "@types/node": "^25.9.5",
22
+ "next": "^16.3.0",
23
23
  "typescript": "^5.9.3",
24
- "vitest": "^4.1.11"
24
+ "vitest": "^3.2.4"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "next": "^14.0.0 || ^15.0.0 || ^16.0.0"