@becklyn/deployment-protection 0.0.1 → 0.2.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.
- package/README.md +17 -12
- package/dist/cjs/constants.d.ts +0 -2
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +1 -5
- package/dist/cjs/handler.d.ts.map +1 -1
- package/dist/cjs/handler.js +7 -5
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -2
- package/dist/cjs/middleware.d.ts +17 -6
- package/dist/cjs/middleware.d.ts.map +1 -1
- package/dist/cjs/middleware.js +54 -13
- package/dist/es/constants.d.ts +0 -2
- package/dist/es/constants.d.ts.map +1 -1
- package/dist/es/constants.js +0 -4
- package/dist/es/handler.d.ts.map +1 -1
- package/dist/es/handler.js +7 -5
- package/dist/es/index.d.ts +1 -1
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +1 -1
- package/dist/es/middleware.d.ts +17 -6
- package/dist/es/middleware.d.ts.map +1 -1
- package/dist/es/middleware.js +54 -12
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -23,33 +23,34 @@ npm i @becklyn/deployment-protection
|
|
|
23
23
|
|
|
24
24
|
### 1. Middleware / proxy
|
|
25
25
|
|
|
26
|
+
Next.js requires `config.matcher` to be a **string literal in the local middleware/proxy file**.
|
|
27
|
+
It cannot be imported from a package (Next analyzes the matcher statically at build time).
|
|
28
|
+
|
|
26
29
|
**Next.js 16+ (`proxy.ts`):**
|
|
27
30
|
|
|
28
31
|
```ts
|
|
29
|
-
import {
|
|
30
|
-
deploymentProtectionMatcher,
|
|
31
|
-
withDeploymentProtection,
|
|
32
|
-
} from "@becklyn/deployment-protection";
|
|
32
|
+
import { withDeploymentProtection } from "@becklyn/deployment-protection";
|
|
33
33
|
|
|
34
34
|
export const proxy = withDeploymentProtection();
|
|
35
35
|
|
|
36
36
|
export const config = {
|
|
37
|
-
matcher:
|
|
37
|
+
matcher: [
|
|
38
|
+
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
39
|
+
],
|
|
38
40
|
};
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
**Next.js ≤15 (`middleware.ts`):**
|
|
42
44
|
|
|
43
45
|
```ts
|
|
44
|
-
import {
|
|
45
|
-
deploymentProtectionMatcher,
|
|
46
|
-
withDeploymentProtection,
|
|
47
|
-
} from "@becklyn/deployment-protection";
|
|
46
|
+
import { withDeploymentProtection } from "@becklyn/deployment-protection";
|
|
48
47
|
|
|
49
48
|
export default withDeploymentProtection();
|
|
50
49
|
|
|
51
50
|
export const config = {
|
|
52
|
-
matcher:
|
|
51
|
+
matcher: [
|
|
52
|
+
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
53
|
+
],
|
|
53
54
|
};
|
|
54
55
|
```
|
|
55
56
|
|
|
@@ -135,7 +136,6 @@ Redeploy. Middleware stays installed but is a no-op.
|
|
|
135
136
|
|
|
136
137
|
```ts
|
|
137
138
|
import {
|
|
138
|
-
deploymentProtectionMatcher,
|
|
139
139
|
handleDeploymentProtection,
|
|
140
140
|
resolveConfig,
|
|
141
141
|
withDeploymentProtection,
|
|
@@ -144,7 +144,12 @@ import {
|
|
|
144
144
|
|
|
145
145
|
- `withDeploymentProtection(options?)` / `withDeploymentProtection(next, options?)` — middleware/proxy factory
|
|
146
146
|
- `handleDeploymentProtection(request, options?)` — framework-agnostic core (`null` = continue)
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
Recommended matcher (must be pasted as a string literal in your middleware/proxy file — see above):
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)";
|
|
152
|
+
```
|
|
148
153
|
|
|
149
154
|
## Security notes
|
|
150
155
|
|
package/dist/cjs/constants.d.ts
CHANGED
|
@@ -10,6 +10,4 @@ export declare const VERCEL_CALLBACK_PATH = "/_becklyn/deployment-protection/ver
|
|
|
10
10
|
export declare const BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
11
11
|
export declare const SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
12
12
|
export declare const DEFAULT_SESSION_TTL_SECONDS: number;
|
|
13
|
-
/** Recommended Next.js matcher for full-site protection. */
|
|
14
|
-
export declare const deploymentProtectionMatcher: string[];
|
|
15
13
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AACxD,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AACnE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AACtE,eAAO,MAAM,qBAAqB,2CAAmC,CAAC;AACtE,eAAO,MAAM,oBAAoB,oDAA4C,CAAC;AAE9E,eAAO,MAAM,aAAa,+BAA+B,CAAC;AAC1D,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,eAAO,MAAM,2BAA2B,QAAoB,CAAC
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AACxD,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AACnE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AACtE,eAAO,MAAM,qBAAqB,2CAAmC,CAAC;AACtE,eAAO,MAAM,oBAAoB,oDAA4C,CAAC;AAE9E,eAAO,MAAM,aAAa,+BAA+B,CAAC;AAC1D,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,eAAO,MAAM,2BAA2B,QAAoB,CAAC"}
|
package/dist/cjs/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.DEFAULT_SESSION_TTL_SECONDS = exports.SET_BYPASS_COOKIE_HEADER = exports.BYPASS_HEADER = exports.VERCEL_CALLBACK_PATH = exports.VERCEL_AUTHORIZE_PATH = exports.INTERNAL_PATH_PREFIX = exports.OAUTH_RETURN_COOKIE = exports.OAUTH_VERIFIER_COOKIE = exports.OAUTH_NONCE_COOKIE = exports.OAUTH_STATE_COOKIE = exports.BYPASS_COOKIE_NAME = exports.SESSION_COOKIE_NAME = void 0;
|
|
4
4
|
exports.SESSION_COOKIE_NAME = "__becklyn_dp_session";
|
|
5
5
|
exports.BYPASS_COOKIE_NAME = "__becklyn_dp_bypass";
|
|
6
6
|
exports.OAUTH_STATE_COOKIE = "__becklyn_dp_oauth_state";
|
|
@@ -13,7 +13,3 @@ exports.VERCEL_CALLBACK_PATH = `${exports.INTERNAL_PATH_PREFIX}/vercel/callback`
|
|
|
13
13
|
exports.BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
14
14
|
exports.SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
15
15
|
exports.DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 14; // 14 days
|
|
16
|
-
/** Recommended Next.js matcher for full-site protection. */
|
|
17
|
-
exports.deploymentProtectionMatcher = [
|
|
18
|
-
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
19
|
-
];
|
|
@@ -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;
|
|
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"}
|
package/dist/cjs/handler.js
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { INTERNAL_PATH_PREFIX, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH } from "./constants";
|
|
2
2
|
export { handleDeploymentProtection } from "./handler";
|
|
3
3
|
export { withDeploymentProtection } from "./middleware";
|
|
4
4
|
export { resolveConfig, hasPasswordAuth, hasVercelAuth, isProtectionActive } from "./config";
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC7F,YAAY,EACR,UAAU,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,WAAW,EACX,cAAc,GACjB,MAAM,SAAS,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isProtectionActive = exports.hasVercelAuth = exports.hasPasswordAuth = exports.resolveConfig = exports.withDeploymentProtection = exports.handleDeploymentProtection = exports.VERCEL_CALLBACK_PATH = exports.VERCEL_AUTHORIZE_PATH = exports.INTERNAL_PATH_PREFIX =
|
|
3
|
+
exports.isProtectionActive = exports.hasVercelAuth = exports.hasPasswordAuth = exports.resolveConfig = exports.withDeploymentProtection = exports.handleDeploymentProtection = exports.VERCEL_CALLBACK_PATH = exports.VERCEL_AUTHORIZE_PATH = exports.INTERNAL_PATH_PREFIX = void 0;
|
|
4
4
|
var constants_1 = require("./constants");
|
|
5
|
-
Object.defineProperty(exports, "deploymentProtectionMatcher", { enumerable: true, get: function () { return constants_1.deploymentProtectionMatcher; } });
|
|
6
5
|
Object.defineProperty(exports, "INTERNAL_PATH_PREFIX", { enumerable: true, get: function () { return constants_1.INTERNAL_PATH_PREFIX; } });
|
|
7
6
|
Object.defineProperty(exports, "VERCEL_AUTHORIZE_PATH", { enumerable: true, get: function () { return constants_1.VERCEL_AUTHORIZE_PATH; } });
|
|
8
7
|
Object.defineProperty(exports, "VERCEL_CALLBACK_PATH", { enumerable: true, get: function () { return constants_1.VERCEL_CALLBACK_PATH; } });
|
package/dist/cjs/middleware.d.ts
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { type NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { deploymentProtectionMatcher } from "./constants";
|
|
3
2
|
import type { DeploymentProtectionOptions } from "./types";
|
|
4
3
|
type NextMiddleware = (request: NextRequest, event?: unknown) => Response | NextResponse | Promise<Response | NextResponse | undefined | void> | undefined | void;
|
|
5
4
|
export type WithDeploymentProtectionArgs = [] | [DeploymentProtectionOptions] | [NextMiddleware] | [NextMiddleware, DeploymentProtectionOptions];
|
|
6
5
|
/**
|
|
7
6
|
* Next.js middleware / proxy wrapper.
|
|
8
7
|
*
|
|
8
|
+
* Next.js only accepts a statically analyzable `config.matcher` in the local
|
|
9
|
+
* middleware/proxy file. Do not import a matcher from this package — paste the
|
|
10
|
+
* recommended pattern inline (see README).
|
|
11
|
+
*
|
|
9
12
|
* @example middleware.ts (Next ≤15)
|
|
10
13
|
* ```ts
|
|
11
|
-
* import {withDeploymentProtection
|
|
14
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
12
15
|
* export default withDeploymentProtection();
|
|
13
|
-
* export const config = {
|
|
16
|
+
* export const config = {
|
|
17
|
+
* matcher: [
|
|
18
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
19
|
+
* ],
|
|
20
|
+
* };
|
|
14
21
|
* ```
|
|
15
22
|
*
|
|
16
23
|
* @example proxy.ts (Next 16+)
|
|
17
24
|
* ```ts
|
|
18
|
-
* import {withDeploymentProtection
|
|
25
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
19
26
|
* export const proxy = withDeploymentProtection();
|
|
20
|
-
* export const config = {
|
|
27
|
+
* export const config = {
|
|
28
|
+
* matcher: [
|
|
29
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
30
|
+
* ],
|
|
31
|
+
* };
|
|
21
32
|
* ```
|
|
22
33
|
*/
|
|
23
34
|
export declare function withDeploymentProtection(...args: WithDeploymentProtectionArgs): (request: NextRequest, event?: unknown) => Promise<Response | NextResponse>;
|
|
24
|
-
export {
|
|
35
|
+
export {};
|
|
25
36
|
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -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;
|
|
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;AAE7D,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CActC"}
|
package/dist/cjs/middleware.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deploymentProtectionMatcher = void 0;
|
|
4
3
|
exports.withDeploymentProtection = withDeploymentProtection;
|
|
5
4
|
const server_1 = require("next/server");
|
|
6
|
-
const constants_1 = require("./constants");
|
|
7
|
-
Object.defineProperty(exports, "deploymentProtectionMatcher", { enumerable: true, get: function () { return constants_1.deploymentProtectionMatcher; } });
|
|
8
5
|
const handler_1 = require("./handler");
|
|
9
6
|
function isOptions(value) {
|
|
10
7
|
return (typeof value === "object" &&
|
|
@@ -12,21 +9,70 @@ function isOptions(value) {
|
|
|
12
9
|
!Array.isArray(value) &&
|
|
13
10
|
typeof value !== "function");
|
|
14
11
|
}
|
|
12
|
+
function toNextResponse(request, response) {
|
|
13
|
+
const location = response.headers.get("Location");
|
|
14
|
+
if (location && response.status >= 300 && response.status < 400) {
|
|
15
|
+
// Always resolve against the incoming request so relative Locations never reach Next.
|
|
16
|
+
const redirectResponse = server_1.NextResponse.redirect(new URL(location, request.url), response.status);
|
|
17
|
+
copyHeaders(response, redirectResponse, /* skipLocation */ true);
|
|
18
|
+
return redirectResponse;
|
|
19
|
+
}
|
|
20
|
+
const nextResponse = new server_1.NextResponse(response.body, {
|
|
21
|
+
status: response.status,
|
|
22
|
+
statusText: response.statusText,
|
|
23
|
+
});
|
|
24
|
+
copyHeaders(response, nextResponse, false);
|
|
25
|
+
return nextResponse;
|
|
26
|
+
}
|
|
27
|
+
function copyHeaders(from, to, skipLocation) {
|
|
28
|
+
const setCookies = typeof from.headers.getSetCookie === "function" ? from.headers.getSetCookie() : [];
|
|
29
|
+
if (setCookies.length > 0) {
|
|
30
|
+
for (const cookie of setCookies) {
|
|
31
|
+
to.headers.append("Set-Cookie", cookie);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
from.headers.forEach((value, key) => {
|
|
35
|
+
const lower = key.toLowerCase();
|
|
36
|
+
if (lower === "set-cookie") {
|
|
37
|
+
// Already copied via getSetCookie when available; fall back otherwise.
|
|
38
|
+
if (setCookies.length === 0) {
|
|
39
|
+
to.headers.append("Set-Cookie", value);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (skipLocation && lower === "location") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
to.headers.set(key, value);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
15
49
|
/**
|
|
16
50
|
* Next.js middleware / proxy wrapper.
|
|
17
51
|
*
|
|
52
|
+
* Next.js only accepts a statically analyzable `config.matcher` in the local
|
|
53
|
+
* middleware/proxy file. Do not import a matcher from this package — paste the
|
|
54
|
+
* recommended pattern inline (see README).
|
|
55
|
+
*
|
|
18
56
|
* @example middleware.ts (Next ≤15)
|
|
19
57
|
* ```ts
|
|
20
|
-
* import {withDeploymentProtection
|
|
58
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
21
59
|
* export default withDeploymentProtection();
|
|
22
|
-
* export const config = {
|
|
60
|
+
* export const config = {
|
|
61
|
+
* matcher: [
|
|
62
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
63
|
+
* ],
|
|
64
|
+
* };
|
|
23
65
|
* ```
|
|
24
66
|
*
|
|
25
67
|
* @example proxy.ts (Next 16+)
|
|
26
68
|
* ```ts
|
|
27
|
-
* import {withDeploymentProtection
|
|
69
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
28
70
|
* export const proxy = withDeploymentProtection();
|
|
29
|
-
* export const config = {
|
|
71
|
+
* export const config = {
|
|
72
|
+
* matcher: [
|
|
73
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
74
|
+
* ],
|
|
75
|
+
* };
|
|
30
76
|
* ```
|
|
31
77
|
*/
|
|
32
78
|
function withDeploymentProtection(...args) {
|
|
@@ -47,12 +93,7 @@ function withDeploymentProtection(...args) {
|
|
|
47
93
|
return async function deploymentProtectionMiddleware(request, event) {
|
|
48
94
|
const protectionResponse = await (0, handler_1.handleDeploymentProtection)(request, options);
|
|
49
95
|
if (protectionResponse) {
|
|
50
|
-
|
|
51
|
-
return new server_1.NextResponse(protectionResponse.body, {
|
|
52
|
-
status: protectionResponse.status,
|
|
53
|
-
statusText: protectionResponse.statusText,
|
|
54
|
-
headers: protectionResponse.headers,
|
|
55
|
-
});
|
|
96
|
+
return toNextResponse(request, protectionResponse);
|
|
56
97
|
}
|
|
57
98
|
if (next) {
|
|
58
99
|
const result = await next(request, event);
|
package/dist/es/constants.d.ts
CHANGED
|
@@ -10,6 +10,4 @@ export declare const VERCEL_CALLBACK_PATH = "/_becklyn/deployment-protection/ver
|
|
|
10
10
|
export declare const BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
11
11
|
export declare const SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
12
12
|
export declare const DEFAULT_SESSION_TTL_SECONDS: number;
|
|
13
|
-
/** Recommended Next.js matcher for full-site protection. */
|
|
14
|
-
export declare const deploymentProtectionMatcher: string[];
|
|
15
13
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AACxD,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AACnE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AACtE,eAAO,MAAM,qBAAqB,2CAAmC,CAAC;AACtE,eAAO,MAAM,oBAAoB,oDAA4C,CAAC;AAE9E,eAAO,MAAM,aAAa,+BAA+B,CAAC;AAC1D,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,eAAO,MAAM,2BAA2B,QAAoB,CAAC
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AACxD,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAC7D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AACnE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AACtE,eAAO,MAAM,qBAAqB,2CAAmC,CAAC;AACtE,eAAO,MAAM,oBAAoB,oDAA4C,CAAC;AAE9E,eAAO,MAAM,aAAa,+BAA+B,CAAC;AAC1D,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE,eAAO,MAAM,2BAA2B,QAAoB,CAAC"}
|
package/dist/es/constants.js
CHANGED
|
@@ -10,7 +10,3 @@ export const VERCEL_CALLBACK_PATH = `${INTERNAL_PATH_PREFIX}/vercel/callback`;
|
|
|
10
10
|
export const BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
11
11
|
export const SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
12
12
|
export const DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 14; // 14 days
|
|
13
|
-
/** Recommended Next.js matcher for full-site protection. */
|
|
14
|
-
export const deploymentProtectionMatcher = [
|
|
15
|
-
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
16
|
-
];
|
package/dist/es/handler.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/es/handler.js
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/dist/es/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { INTERNAL_PATH_PREFIX, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH } from "./constants";
|
|
2
2
|
export { handleDeploymentProtection } from "./handler";
|
|
3
3
|
export { withDeploymentProtection } from "./middleware";
|
|
4
4
|
export { resolveConfig, hasPasswordAuth, hasVercelAuth, isProtectionActive } from "./config";
|
package/dist/es/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC7F,YAAY,EACR,UAAU,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,WAAW,EACX,cAAc,GACjB,MAAM,SAAS,CAAC"}
|
package/dist/es/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { INTERNAL_PATH_PREFIX, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH } from "./constants";
|
|
2
2
|
export { handleDeploymentProtection } from "./handler";
|
|
3
3
|
export { withDeploymentProtection } from "./middleware";
|
|
4
4
|
export { resolveConfig, hasPasswordAuth, hasVercelAuth, isProtectionActive } from "./config";
|
package/dist/es/middleware.d.ts
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { type NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { deploymentProtectionMatcher } from "./constants";
|
|
3
2
|
import type { DeploymentProtectionOptions } from "./types";
|
|
4
3
|
type NextMiddleware = (request: NextRequest, event?: unknown) => Response | NextResponse | Promise<Response | NextResponse | undefined | void> | undefined | void;
|
|
5
4
|
export type WithDeploymentProtectionArgs = [] | [DeploymentProtectionOptions] | [NextMiddleware] | [NextMiddleware, DeploymentProtectionOptions];
|
|
6
5
|
/**
|
|
7
6
|
* Next.js middleware / proxy wrapper.
|
|
8
7
|
*
|
|
8
|
+
* Next.js only accepts a statically analyzable `config.matcher` in the local
|
|
9
|
+
* middleware/proxy file. Do not import a matcher from this package — paste the
|
|
10
|
+
* recommended pattern inline (see README).
|
|
11
|
+
*
|
|
9
12
|
* @example middleware.ts (Next ≤15)
|
|
10
13
|
* ```ts
|
|
11
|
-
* import {withDeploymentProtection
|
|
14
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
12
15
|
* export default withDeploymentProtection();
|
|
13
|
-
* export const config = {
|
|
16
|
+
* export const config = {
|
|
17
|
+
* matcher: [
|
|
18
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
19
|
+
* ],
|
|
20
|
+
* };
|
|
14
21
|
* ```
|
|
15
22
|
*
|
|
16
23
|
* @example proxy.ts (Next 16+)
|
|
17
24
|
* ```ts
|
|
18
|
-
* import {withDeploymentProtection
|
|
25
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
19
26
|
* export const proxy = withDeploymentProtection();
|
|
20
|
-
* export const config = {
|
|
27
|
+
* export const config = {
|
|
28
|
+
* matcher: [
|
|
29
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
30
|
+
* ],
|
|
31
|
+
* };
|
|
21
32
|
* ```
|
|
22
33
|
*/
|
|
23
34
|
export declare function withDeploymentProtection(...args: WithDeploymentProtectionArgs): (request: NextRequest, event?: unknown) => Promise<Response | NextResponse>;
|
|
24
|
-
export {
|
|
35
|
+
export {};
|
|
25
36
|
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -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;
|
|
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;AAE7D,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,IAAI,EAAE,4BAA4B,IAgBtE,SAAS,WAAW,EACpB,QAAQ,OAAO,KAChB,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CActC"}
|
package/dist/es/middleware.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { deploymentProtectionMatcher } from "./constants";
|
|
3
2
|
import { handleDeploymentProtection } from "./handler";
|
|
4
3
|
function isOptions(value) {
|
|
5
4
|
return (typeof value === "object" &&
|
|
@@ -7,21 +6,70 @@ function isOptions(value) {
|
|
|
7
6
|
!Array.isArray(value) &&
|
|
8
7
|
typeof value !== "function");
|
|
9
8
|
}
|
|
9
|
+
function toNextResponse(request, response) {
|
|
10
|
+
const location = response.headers.get("Location");
|
|
11
|
+
if (location && response.status >= 300 && response.status < 400) {
|
|
12
|
+
// Always resolve against the incoming request so relative Locations never reach Next.
|
|
13
|
+
const redirectResponse = NextResponse.redirect(new URL(location, request.url), response.status);
|
|
14
|
+
copyHeaders(response, redirectResponse, /* skipLocation */ true);
|
|
15
|
+
return redirectResponse;
|
|
16
|
+
}
|
|
17
|
+
const nextResponse = new NextResponse(response.body, {
|
|
18
|
+
status: response.status,
|
|
19
|
+
statusText: response.statusText,
|
|
20
|
+
});
|
|
21
|
+
copyHeaders(response, nextResponse, false);
|
|
22
|
+
return nextResponse;
|
|
23
|
+
}
|
|
24
|
+
function copyHeaders(from, to, skipLocation) {
|
|
25
|
+
const setCookies = typeof from.headers.getSetCookie === "function" ? from.headers.getSetCookie() : [];
|
|
26
|
+
if (setCookies.length > 0) {
|
|
27
|
+
for (const cookie of setCookies) {
|
|
28
|
+
to.headers.append("Set-Cookie", cookie);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
from.headers.forEach((value, key) => {
|
|
32
|
+
const lower = key.toLowerCase();
|
|
33
|
+
if (lower === "set-cookie") {
|
|
34
|
+
// Already copied via getSetCookie when available; fall back otherwise.
|
|
35
|
+
if (setCookies.length === 0) {
|
|
36
|
+
to.headers.append("Set-Cookie", value);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (skipLocation && lower === "location") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
to.headers.set(key, value);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
10
46
|
/**
|
|
11
47
|
* Next.js middleware / proxy wrapper.
|
|
12
48
|
*
|
|
49
|
+
* Next.js only accepts a statically analyzable `config.matcher` in the local
|
|
50
|
+
* middleware/proxy file. Do not import a matcher from this package — paste the
|
|
51
|
+
* recommended pattern inline (see README).
|
|
52
|
+
*
|
|
13
53
|
* @example middleware.ts (Next ≤15)
|
|
14
54
|
* ```ts
|
|
15
|
-
* import {withDeploymentProtection
|
|
55
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
16
56
|
* export default withDeploymentProtection();
|
|
17
|
-
* export const config = {
|
|
57
|
+
* export const config = {
|
|
58
|
+
* matcher: [
|
|
59
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
60
|
+
* ],
|
|
61
|
+
* };
|
|
18
62
|
* ```
|
|
19
63
|
*
|
|
20
64
|
* @example proxy.ts (Next 16+)
|
|
21
65
|
* ```ts
|
|
22
|
-
* import {withDeploymentProtection
|
|
66
|
+
* import {withDeploymentProtection} from "@becklyn/deployment-protection";
|
|
23
67
|
* export const proxy = withDeploymentProtection();
|
|
24
|
-
* export const config = {
|
|
68
|
+
* export const config = {
|
|
69
|
+
* matcher: [
|
|
70
|
+
* "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?)$).*)",
|
|
71
|
+
* ],
|
|
72
|
+
* };
|
|
25
73
|
* ```
|
|
26
74
|
*/
|
|
27
75
|
export function withDeploymentProtection(...args) {
|
|
@@ -42,12 +90,7 @@ export function withDeploymentProtection(...args) {
|
|
|
42
90
|
return async function deploymentProtectionMiddleware(request, event) {
|
|
43
91
|
const protectionResponse = await handleDeploymentProtection(request, options);
|
|
44
92
|
if (protectionResponse) {
|
|
45
|
-
|
|
46
|
-
return new NextResponse(protectionResponse.body, {
|
|
47
|
-
status: protectionResponse.status,
|
|
48
|
-
statusText: protectionResponse.statusText,
|
|
49
|
-
headers: protectionResponse.headers,
|
|
50
|
-
});
|
|
93
|
+
return toNextResponse(request, protectionResponse);
|
|
51
94
|
}
|
|
52
95
|
if (next) {
|
|
53
96
|
const result = await next(request, event);
|
|
@@ -56,4 +99,3 @@ export function withDeploymentProtection(...args) {
|
|
|
56
99
|
return NextResponse.next();
|
|
57
100
|
};
|
|
58
101
|
}
|
|
59
|
-
export { deploymentProtectionMatcher };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@becklyn/deployment-protection",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.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": "^
|
|
22
|
-
"next": "^16.3.
|
|
21
|
+
"@types/node": "^25.9.5",
|
|
22
|
+
"next": "^16.3.0",
|
|
23
23
|
"typescript": "^5.9.3",
|
|
24
|
-
"vitest": "^
|
|
24
|
+
"vitest": "^3.2.4"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0"
|