@becklyn/deployment-protection 0.0.1
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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/cjs/bypass.d.ts +5 -0
- package/dist/cjs/bypass.d.ts.map +1 -0
- package/dist/cjs/bypass.js +51 -0
- package/dist/cjs/config.d.ts +13 -0
- package/dist/cjs/config.d.ts.map +1 -0
- package/dist/cjs/config.js +68 -0
- package/dist/cjs/constants.d.ts +15 -0
- package/dist/cjs/constants.d.ts.map +1 -0
- package/dist/cjs/constants.js +19 -0
- package/dist/cjs/crypto.d.ts +7 -0
- package/dist/cjs/crypto.d.ts.map +1 -0
- package/dist/cjs/crypto.js +56 -0
- package/dist/cjs/handler.d.ts +6 -0
- package/dist/cjs/handler.d.ts.map +1 -0
- package/dist/cjs/handler.js +212 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/login-page.d.ts +8 -0
- package/dist/cjs/login-page.d.ts.map +1 -0
- package/dist/cjs/login-page.js +176 -0
- package/dist/cjs/middleware.d.ts +25 -0
- package/dist/cjs/middleware.d.ts.map +1 -0
- package/dist/cjs/middleware.js +63 -0
- package/dist/cjs/password.d.ts +3 -0
- package/dist/cjs/password.d.ts.map +1 -0
- package/dist/cjs/password.js +12 -0
- package/dist/cjs/safe-return-to.d.ts +8 -0
- package/dist/cjs/safe-return-to.d.ts.map +1 -0
- package/dist/cjs/safe-return-to.js +60 -0
- package/dist/cjs/session.d.ts +11 -0
- package/dist/cjs/session.d.ts.map +1 -0
- package/dist/cjs/session.js +64 -0
- package/dist/cjs/types.d.ts +35 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/vercel-oauth.d.ts +30 -0
- package/dist/cjs/vercel-oauth.d.ts.map +1 -0
- package/dist/cjs/vercel-oauth.js +166 -0
- package/dist/es/bypass.d.ts +5 -0
- package/dist/es/bypass.d.ts.map +1 -0
- package/dist/es/bypass.js +45 -0
- package/dist/es/config.d.ts +13 -0
- package/dist/es/config.d.ts.map +1 -0
- package/dist/es/config.js +62 -0
- package/dist/es/constants.d.ts +15 -0
- package/dist/es/constants.d.ts.map +1 -0
- package/dist/es/constants.js +16 -0
- package/dist/es/crypto.d.ts +7 -0
- package/dist/es/crypto.d.ts.map +1 -0
- package/dist/es/crypto.js +48 -0
- package/dist/es/handler.d.ts +6 -0
- package/dist/es/handler.d.ts.map +1 -0
- package/dist/es/handler.js +209 -0
- package/dist/es/index.d.ts +6 -0
- package/dist/es/index.d.ts.map +1 -0
- package/dist/es/index.js +4 -0
- package/dist/es/login-page.d.ts +8 -0
- package/dist/es/login-page.d.ts.map +1 -0
- package/dist/es/login-page.js +173 -0
- package/dist/es/middleware.d.ts +25 -0
- package/dist/es/middleware.d.ts.map +1 -0
- package/dist/es/middleware.js +59 -0
- package/dist/es/password.d.ts +3 -0
- package/dist/es/password.d.ts.map +1 -0
- package/dist/es/password.js +9 -0
- package/dist/es/safe-return-to.d.ts +8 -0
- package/dist/es/safe-return-to.d.ts.map +1 -0
- package/dist/es/safe-return-to.js +57 -0
- package/dist/es/session.d.ts +11 -0
- package/dist/es/session.d.ts.map +1 -0
- package/dist/es/session.js +59 -0
- package/dist/es/types.d.ts +35 -0
- package/dist/es/types.d.ts.map +1 -0
- package/dist/es/types.js +1 -0
- package/dist/es/vercel-oauth.d.ts +30 -0
- package/dist/es/vercel-oauth.d.ts.map +1 -0
- package/dist/es/vercel-oauth.js +156 -0
- package/package.json +46 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildVercelAuthorizeRedirect = buildVercelAuthorizeRedirect;
|
|
4
|
+
exports.exchangeVercelCode = exchangeVercelCode;
|
|
5
|
+
exports.fetchVercelUserInfo = fetchVercelUserInfo;
|
|
6
|
+
exports.readCookie = readCookie;
|
|
7
|
+
exports.assertOAuthState = assertOAuthState;
|
|
8
|
+
exports.decodeIdTokenNonce = decodeIdTokenNonce;
|
|
9
|
+
exports.clearOAuthCookies = clearOAuthCookies;
|
|
10
|
+
exports.appendSetCookie = appendSetCookie;
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const crypto_1 = require("./crypto");
|
|
13
|
+
function cookieOptions(secure) {
|
|
14
|
+
return {
|
|
15
|
+
httpOnly: true,
|
|
16
|
+
sameSite: "lax",
|
|
17
|
+
secure,
|
|
18
|
+
path: "/",
|
|
19
|
+
maxAge: 10 * 60,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function buildVercelAuthorizeRedirect(request, config, returnTo) {
|
|
23
|
+
if (!config.vercelClientId) {
|
|
24
|
+
return new Response("Vercel OAuth is not configured", { status: 500 });
|
|
25
|
+
}
|
|
26
|
+
const state = (0, crypto_1.randomToken)(32);
|
|
27
|
+
const nonce = (0, crypto_1.randomToken)(32);
|
|
28
|
+
const codeVerifier = (0, crypto_1.randomToken)(48);
|
|
29
|
+
const codeChallenge = await (0, crypto_1.sha256Base64Url)(codeVerifier);
|
|
30
|
+
const origin = new URL(request.url).origin;
|
|
31
|
+
const redirectUri = `${origin}${constants_1.VERCEL_CALLBACK_PATH}`;
|
|
32
|
+
const secure = origin.startsWith("https://");
|
|
33
|
+
const params = new URLSearchParams({
|
|
34
|
+
client_id: config.vercelClientId,
|
|
35
|
+
redirect_uri: redirectUri,
|
|
36
|
+
state,
|
|
37
|
+
nonce,
|
|
38
|
+
code_challenge: codeChallenge,
|
|
39
|
+
code_challenge_method: "S256",
|
|
40
|
+
response_type: "code",
|
|
41
|
+
scope: "openid email profile",
|
|
42
|
+
});
|
|
43
|
+
const response = new Response(null, {
|
|
44
|
+
status: 302,
|
|
45
|
+
headers: {
|
|
46
|
+
Location: `https://vercel.com/oauth/authorize?${params.toString()}`,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const opts = cookieOptions(secure);
|
|
50
|
+
appendSetCookie(response, constants_1.OAUTH_STATE_COOKIE, state, opts);
|
|
51
|
+
appendSetCookie(response, constants_1.OAUTH_NONCE_COOKIE, nonce, opts);
|
|
52
|
+
appendSetCookie(response, constants_1.OAUTH_VERIFIER_COOKIE, codeVerifier, opts);
|
|
53
|
+
appendSetCookie(response, constants_1.OAUTH_RETURN_COOKIE, returnTo || "/", opts);
|
|
54
|
+
return response;
|
|
55
|
+
}
|
|
56
|
+
async function exchangeVercelCode(request, config, code, codeVerifier) {
|
|
57
|
+
if (!config.vercelClientId || !config.vercelClientSecret) {
|
|
58
|
+
throw new Error("Vercel OAuth is not configured");
|
|
59
|
+
}
|
|
60
|
+
const origin = new URL(request.url).origin;
|
|
61
|
+
const body = new URLSearchParams({
|
|
62
|
+
grant_type: "authorization_code",
|
|
63
|
+
client_id: config.vercelClientId,
|
|
64
|
+
client_secret: config.vercelClientSecret,
|
|
65
|
+
code,
|
|
66
|
+
code_verifier: codeVerifier,
|
|
67
|
+
redirect_uri: `${origin}${constants_1.VERCEL_CALLBACK_PATH}`,
|
|
68
|
+
});
|
|
69
|
+
const response = await fetch("https://api.vercel.com/login/oauth/token", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
73
|
+
},
|
|
74
|
+
body,
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorText = await response.text();
|
|
78
|
+
throw new Error(`Token exchange failed: ${errorText}`);
|
|
79
|
+
}
|
|
80
|
+
return (await response.json());
|
|
81
|
+
}
|
|
82
|
+
async function fetchVercelUserInfo(accessToken) {
|
|
83
|
+
const response = await fetch("https://api.vercel.com/login/oauth/userinfo", {
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: `Bearer ${accessToken}`,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error("Failed to load Vercel user info");
|
|
90
|
+
}
|
|
91
|
+
return (await response.json());
|
|
92
|
+
}
|
|
93
|
+
function readCookie(request, name) {
|
|
94
|
+
const cookieHeader = request.headers.get("cookie");
|
|
95
|
+
if (!cookieHeader) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
for (const part of cookieHeader.split(";")) {
|
|
99
|
+
const [rawName, ...rest] = part.trim().split("=");
|
|
100
|
+
if (rawName === name) {
|
|
101
|
+
return decodeURIComponent(rest.join("="));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
async function assertOAuthState(request, state) {
|
|
107
|
+
const stored = readCookie(request, constants_1.OAUTH_STATE_COOKIE);
|
|
108
|
+
if (!state || !stored) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return (0, crypto_1.timingSafeEqualString)(state, stored);
|
|
112
|
+
}
|
|
113
|
+
function decodeIdTokenNonce(idToken) {
|
|
114
|
+
if (!idToken) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const parts = idToken.split(".");
|
|
118
|
+
const payload = parts[1];
|
|
119
|
+
if (!payload) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const padded = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
124
|
+
const padLength = (4 - (padded.length % 4)) % 4;
|
|
125
|
+
const json = atob(padded + "=".repeat(padLength));
|
|
126
|
+
const data = JSON.parse(json);
|
|
127
|
+
return data.nonce ?? null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function clearOAuthCookies(response, secure) {
|
|
134
|
+
const expired = {
|
|
135
|
+
httpOnly: true,
|
|
136
|
+
sameSite: "lax",
|
|
137
|
+
secure,
|
|
138
|
+
path: "/",
|
|
139
|
+
maxAge: 0,
|
|
140
|
+
};
|
|
141
|
+
for (const name of [
|
|
142
|
+
constants_1.OAUTH_STATE_COOKIE,
|
|
143
|
+
constants_1.OAUTH_NONCE_COOKIE,
|
|
144
|
+
constants_1.OAUTH_VERIFIER_COOKIE,
|
|
145
|
+
constants_1.OAUTH_RETURN_COOKIE,
|
|
146
|
+
]) {
|
|
147
|
+
appendSetCookie(response, name, "", expired);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function appendSetCookie(response, name, value, options) {
|
|
151
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
152
|
+
if (options.maxAge !== undefined) {
|
|
153
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
154
|
+
}
|
|
155
|
+
parts.push(`Path=${options.path ?? "/"}`);
|
|
156
|
+
if (options.httpOnly) {
|
|
157
|
+
parts.push("HttpOnly");
|
|
158
|
+
}
|
|
159
|
+
if (options.secure) {
|
|
160
|
+
parts.push("Secure");
|
|
161
|
+
}
|
|
162
|
+
if (options.sameSite) {
|
|
163
|
+
parts.push(`SameSite=${options.sameSite === "none" ? "None" : options.sameSite[0].toUpperCase()}${options.sameSite.slice(1)}`);
|
|
164
|
+
}
|
|
165
|
+
response.headers.append("Set-Cookie", parts.join("; "));
|
|
166
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function readBypassToken(request: Request): string | null;
|
|
2
|
+
export declare function readBypassCookie(request: Request): string | null;
|
|
3
|
+
export declare function isValidBypass(provided: string | null | undefined, secret: string | null): Promise<boolean>;
|
|
4
|
+
export declare function shouldSetBypassCookie(request: Request): boolean | "samesitenone";
|
|
5
|
+
//# sourceMappingURL=bypass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bypass.d.ts","sourceRoot":"","sources":["../../src/bypass.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAehE;AAED,wBAAsB,aAAa,CAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,cAAc,CAehF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BYPASS_COOKIE_NAME, BYPASS_HEADER, SET_BYPASS_COOKIE_HEADER } from "./constants";
|
|
2
|
+
import { timingSafeEqualString } from "./crypto";
|
|
3
|
+
export function readBypassToken(request) {
|
|
4
|
+
const header = request.headers.get(BYPASS_HEADER)?.trim();
|
|
5
|
+
if (header) {
|
|
6
|
+
return header;
|
|
7
|
+
}
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
const query = url.searchParams.get(BYPASS_HEADER)?.trim();
|
|
10
|
+
if (query) {
|
|
11
|
+
return query;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
export function readBypassCookie(request) {
|
|
16
|
+
const cookieHeader = request.headers.get("cookie");
|
|
17
|
+
if (!cookieHeader) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
for (const part of cookieHeader.split(";")) {
|
|
21
|
+
const [rawName, ...rest] = part.trim().split("=");
|
|
22
|
+
if (rawName === BYPASS_COOKIE_NAME) {
|
|
23
|
+
return decodeURIComponent(rest.join("="));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
export async function isValidBypass(provided, secret) {
|
|
29
|
+
if (!provided || !secret) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return timingSafeEqualString(provided, secret);
|
|
33
|
+
}
|
|
34
|
+
export function shouldSetBypassCookie(request) {
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
const value = request.headers.get(SET_BYPASS_COOKIE_HEADER) ??
|
|
37
|
+
url.searchParams.get(SET_BYPASS_COOKIE_HEADER);
|
|
38
|
+
if (!value) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (value.toLowerCase() === "samesitenone") {
|
|
42
|
+
return "samesitenone";
|
|
43
|
+
}
|
|
44
|
+
return value === "true" || value === "1";
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DeploymentProtectionConfig, DeploymentProtectionOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve runtime configuration from env (+ optional overrides).
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveConfig(options?: DeploymentProtectionOptions): DeploymentProtectionConfig;
|
|
6
|
+
export declare function hasPasswordAuth(config: DeploymentProtectionConfig): boolean;
|
|
7
|
+
export declare function hasVercelAuth(config: DeploymentProtectionConfig): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Protection only runs when enabled and at least one auth method is configured.
|
|
10
|
+
* Misconfigured+enabled is treated as inactive (fail-open) so local/dev isn't bricked.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isProtectionActive(config: DeploymentProtectionConfig): boolean;
|
|
13
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,2BAA2B,EAAU,MAAM,SAAS,CAAC;AA0B/F;;GAEG;AACH,wBAAgB,aAAa,CACzB,OAAO,GAAE,2BAAgC,GAC1C,0BAA0B,CA6C5B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAE3E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAEzE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAK9E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const FALSEY = new Set(["0", "false", "no", "off"]);
|
|
2
|
+
function read(env, ...keys) {
|
|
3
|
+
for (const key of keys) {
|
|
4
|
+
const value = env[key]?.trim();
|
|
5
|
+
if (value) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function isEnabled(env) {
|
|
12
|
+
const raw = read(env, "DEPLOYMENT_PROTECTION_ENABLED");
|
|
13
|
+
if (!raw) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return !FALSEY.has(raw.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve runtime configuration from env (+ optional overrides).
|
|
20
|
+
*/
|
|
21
|
+
export function resolveConfig(options = {}) {
|
|
22
|
+
const env = {
|
|
23
|
+
...(typeof process !== "undefined" ? process.env : {}),
|
|
24
|
+
...options.env,
|
|
25
|
+
};
|
|
26
|
+
const username = read(env, "DEPLOYMENT_PROTECTION_USERNAME");
|
|
27
|
+
const password = read(env, "DEPLOYMENT_PROTECTION_PASSWORD");
|
|
28
|
+
const explicitSecret = read(env, "DEPLOYMENT_PROTECTION_SECRET");
|
|
29
|
+
// Prefer an explicit secret; otherwise derive a stable key from credentials so
|
|
30
|
+
// projects can stay zero-config beyond username/password.
|
|
31
|
+
const secret = explicitSecret ?? (username && password ? `dp:${username}:${password}` : null);
|
|
32
|
+
return {
|
|
33
|
+
enabled: isEnabled(env),
|
|
34
|
+
username,
|
|
35
|
+
password,
|
|
36
|
+
secret,
|
|
37
|
+
bypassSecret: read(env, "VERCEL_AUTOMATION_BYPASS_SECRET", "DEPLOYMENT_PROTECTION_BYPASS_SECRET"),
|
|
38
|
+
vercelClientId: read(env, "DEPLOYMENT_PROTECTION_VERCEL_CLIENT_ID", "NEXT_PUBLIC_VERCEL_APP_CLIENT_ID", "VERCEL_APP_CLIENT_ID"),
|
|
39
|
+
vercelClientSecret: read(env, "DEPLOYMENT_PROTECTION_VERCEL_CLIENT_SECRET", "VERCEL_APP_CLIENT_SECRET"),
|
|
40
|
+
sessionTtlSeconds: options.sessionTtlSeconds ?? 60 * 60 * 24 * 14,
|
|
41
|
+
formTitle: options.form?.title ??
|
|
42
|
+
read(env, "DEPLOYMENT_PROTECTION_FORM_TITLE") ??
|
|
43
|
+
"Authentication required",
|
|
44
|
+
formDescription: options.form?.description ??
|
|
45
|
+
read(env, "DEPLOYMENT_PROTECTION_FORM_DESCRIPTION") ??
|
|
46
|
+
"Enter the shared credentials to continue.",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function hasPasswordAuth(config) {
|
|
50
|
+
return Boolean(config.username && config.password && config.secret);
|
|
51
|
+
}
|
|
52
|
+
export function hasVercelAuth(config) {
|
|
53
|
+
return Boolean(config.vercelClientId && config.vercelClientSecret && config.secret);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Protection only runs when enabled and at least one auth method is configured.
|
|
57
|
+
* Misconfigured+enabled is treated as inactive (fail-open) so local/dev isn't bricked.
|
|
58
|
+
*/
|
|
59
|
+
export function isProtectionActive(config) {
|
|
60
|
+
return (config.enabled &&
|
|
61
|
+
(hasPasswordAuth(config) || hasVercelAuth(config) || Boolean(config.bypassSecret)));
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const SESSION_COOKIE_NAME = "__becklyn_dp_session";
|
|
2
|
+
export declare const BYPASS_COOKIE_NAME = "__becklyn_dp_bypass";
|
|
3
|
+
export declare const OAUTH_STATE_COOKIE = "__becklyn_dp_oauth_state";
|
|
4
|
+
export declare const OAUTH_NONCE_COOKIE = "__becklyn_dp_oauth_nonce";
|
|
5
|
+
export declare const OAUTH_VERIFIER_COOKIE = "__becklyn_dp_oauth_verifier";
|
|
6
|
+
export declare const OAUTH_RETURN_COOKIE = "__becklyn_dp_oauth_return";
|
|
7
|
+
export declare const INTERNAL_PATH_PREFIX = "/_becklyn/deployment-protection";
|
|
8
|
+
export declare const VERCEL_AUTHORIZE_PATH = "/_becklyn/deployment-protection/vercel";
|
|
9
|
+
export declare const VERCEL_CALLBACK_PATH = "/_becklyn/deployment-protection/vercel/callback";
|
|
10
|
+
export declare const BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
11
|
+
export declare const SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
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
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +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;AAE7D,4DAA4D;AAC5D,eAAO,MAAM,2BAA2B,UAEvC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const SESSION_COOKIE_NAME = "__becklyn_dp_session";
|
|
2
|
+
export const BYPASS_COOKIE_NAME = "__becklyn_dp_bypass";
|
|
3
|
+
export const OAUTH_STATE_COOKIE = "__becklyn_dp_oauth_state";
|
|
4
|
+
export const OAUTH_NONCE_COOKIE = "__becklyn_dp_oauth_nonce";
|
|
5
|
+
export const OAUTH_VERIFIER_COOKIE = "__becklyn_dp_oauth_verifier";
|
|
6
|
+
export const OAUTH_RETURN_COOKIE = "__becklyn_dp_oauth_return";
|
|
7
|
+
export const INTERNAL_PATH_PREFIX = "/_becklyn/deployment-protection";
|
|
8
|
+
export const VERCEL_AUTHORIZE_PATH = `${INTERNAL_PATH_PREFIX}/vercel`;
|
|
9
|
+
export const VERCEL_CALLBACK_PATH = `${INTERNAL_PATH_PREFIX}/vercel/callback`;
|
|
10
|
+
export const BYPASS_HEADER = "x-vercel-protection-bypass";
|
|
11
|
+
export const SET_BYPASS_COOKIE_HEADER = "x-vercel-set-bypass-cookie";
|
|
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
|
+
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function bytesToBase64Url(bytes: ArrayBuffer | Uint8Array): string;
|
|
2
|
+
export declare function base64UrlToBytes(value: string): Uint8Array;
|
|
3
|
+
export declare function sha256Base64Url(value: string): Promise<string>;
|
|
4
|
+
export declare function hmacSign(secret: string, payload: string): Promise<string>;
|
|
5
|
+
export declare function timingSafeEqualString(a: string, b: string): Promise<boolean>;
|
|
6
|
+
export declare function randomToken(byteLength?: number): string;
|
|
7
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CASxE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAY1D;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpE;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU/E;AAED,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBlF;AAED,wBAAgB,WAAW,CAAC,UAAU,SAAK,GAAG,MAAM,CAInD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const textEncoder = new TextEncoder();
|
|
2
|
+
export function bytesToBase64Url(bytes) {
|
|
3
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
4
|
+
let binary = "";
|
|
5
|
+
for (const byte of view) {
|
|
6
|
+
binary += String.fromCharCode(byte);
|
|
7
|
+
}
|
|
8
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
9
|
+
}
|
|
10
|
+
export function base64UrlToBytes(value) {
|
|
11
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
12
|
+
const padLength = (4 - (padded.length % 4)) % 4;
|
|
13
|
+
const base64 = padded + "=".repeat(padLength);
|
|
14
|
+
const binary = atob(base64);
|
|
15
|
+
const bytes = new Uint8Array(binary.length);
|
|
16
|
+
for (let i = 0; i < binary.length; i++) {
|
|
17
|
+
bytes[i] = binary.charCodeAt(i);
|
|
18
|
+
}
|
|
19
|
+
return bytes;
|
|
20
|
+
}
|
|
21
|
+
export async function sha256Base64Url(value) {
|
|
22
|
+
const digest = await crypto.subtle.digest("SHA-256", textEncoder.encode(value));
|
|
23
|
+
return bytesToBase64Url(digest);
|
|
24
|
+
}
|
|
25
|
+
export async function hmacSign(secret, payload) {
|
|
26
|
+
const key = await crypto.subtle.importKey("raw", textEncoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
27
|
+
const signature = await crypto.subtle.sign("HMAC", key, textEncoder.encode(payload));
|
|
28
|
+
return bytesToBase64Url(signature);
|
|
29
|
+
}
|
|
30
|
+
export async function timingSafeEqualString(a, b) {
|
|
31
|
+
const aBytes = textEncoder.encode(a);
|
|
32
|
+
const bBytes = textEncoder.encode(b);
|
|
33
|
+
if (aBytes.length !== bBytes.length) {
|
|
34
|
+
// Compare against itself to keep runtime roughly constant on length mismatch.
|
|
35
|
+
await crypto.subtle.digest("SHA-256", aBytes);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
let mismatch = 0;
|
|
39
|
+
for (let i = 0; i < aBytes.length; i++) {
|
|
40
|
+
mismatch |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
|
|
41
|
+
}
|
|
42
|
+
return mismatch === 0;
|
|
43
|
+
}
|
|
44
|
+
export function randomToken(byteLength = 32) {
|
|
45
|
+
const bytes = new Uint8Array(byteLength);
|
|
46
|
+
crypto.getRandomValues(bytes);
|
|
47
|
+
return bytesToBase64Url(bytes);
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DeploymentProtectionOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Core request handler. Returns `null` when the request may continue to the app.
|
|
4
|
+
*/
|
|
5
|
+
export declare function handleDeploymentProtection(request: Request, options?: DeploymentProtectionOptions): Promise<Response | null>;
|
|
6
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { isValidBypass, readBypassCookie, readBypassToken, shouldSetBypassCookie } from "./bypass";
|
|
2
|
+
import { hasPasswordAuth, hasVercelAuth, isProtectionActive, resolveConfig } from "./config";
|
|
3
|
+
import { BYPASS_COOKIE_NAME, BYPASS_HEADER, OAUTH_NONCE_COOKIE, OAUTH_RETURN_COOKIE, OAUTH_VERIFIER_COOKIE, SESSION_COOKIE_NAME, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH, } from "./constants";
|
|
4
|
+
import { timingSafeEqualString } from "./crypto";
|
|
5
|
+
import { renderLoginPage } from "./login-page";
|
|
6
|
+
import { validatePasswordCredentials } from "./password";
|
|
7
|
+
import { safeReturnTo } from "./safe-return-to";
|
|
8
|
+
import { createSessionToken, sessionCookieOptions, verifySessionToken } from "./session";
|
|
9
|
+
import { appendSetCookie, assertOAuthState, buildVercelAuthorizeRedirect, clearOAuthCookies, decodeIdTokenNonce, exchangeVercelCode, fetchVercelUserInfo, readCookie, } from "./vercel-oauth";
|
|
10
|
+
function isSecureRequest(request) {
|
|
11
|
+
return new URL(request.url).protocol === "https:";
|
|
12
|
+
}
|
|
13
|
+
function getPathname(request) {
|
|
14
|
+
return new URL(request.url).pathname;
|
|
15
|
+
}
|
|
16
|
+
function htmlResponse(html, status = 401) {
|
|
17
|
+
return new Response(html, {
|
|
18
|
+
status,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
21
|
+
"Cache-Control": "no-store",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function redirect(location, status = 302) {
|
|
26
|
+
return new Response(null, {
|
|
27
|
+
status,
|
|
28
|
+
headers: {
|
|
29
|
+
Location: location,
|
|
30
|
+
"Cache-Control": "no-store",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function attachSession(response, config, method, subject, secure) {
|
|
35
|
+
if (!config.secret) {
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
const token = await createSessionToken(config.secret, method, subject, config.sessionTtlSeconds);
|
|
39
|
+
const opts = sessionCookieOptions(config.sessionTtlSeconds, secure);
|
|
40
|
+
appendSetCookie(response, SESSION_COOKIE_NAME, token, opts);
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
async function handleBypass(request, config) {
|
|
44
|
+
if (!config.bypassSecret) {
|
|
45
|
+
return { kind: "miss" };
|
|
46
|
+
}
|
|
47
|
+
const token = readBypassToken(request) ?? readBypassCookie(request);
|
|
48
|
+
if (!(await isValidBypass(token, config.bypassSecret))) {
|
|
49
|
+
return { kind: "miss" };
|
|
50
|
+
}
|
|
51
|
+
const setCookieMode = shouldSetBypassCookie(request);
|
|
52
|
+
const url = new URL(request.url);
|
|
53
|
+
const hadQueryBypass = url.searchParams.has(BYPASS_HEADER);
|
|
54
|
+
const hadSetCookieQuery = url.searchParams.has("x-vercel-set-bypass-cookie");
|
|
55
|
+
if (hadQueryBypass || hadSetCookieQuery || setCookieMode) {
|
|
56
|
+
url.searchParams.delete(BYPASS_HEADER);
|
|
57
|
+
url.searchParams.delete("x-vercel-set-bypass-cookie");
|
|
58
|
+
const response = redirect(url.pathname + url.search + url.hash);
|
|
59
|
+
const secure = isSecureRequest(request);
|
|
60
|
+
if (setCookieMode || hadSetCookieQuery) {
|
|
61
|
+
appendSetCookie(response, BYPASS_COOKIE_NAME, config.bypassSecret, {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
secure,
|
|
64
|
+
path: "/",
|
|
65
|
+
maxAge: config.sessionTtlSeconds,
|
|
66
|
+
sameSite: setCookieMode === "samesitenone" ? "none" : "lax",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
await attachSession(response, config, "bypass", "automation", secure);
|
|
70
|
+
return { kind: "respond", response };
|
|
71
|
+
}
|
|
72
|
+
return { kind: "allow" };
|
|
73
|
+
}
|
|
74
|
+
async function handlePasswordPost(request, config) {
|
|
75
|
+
if (request.method !== "POST" || !hasPasswordAuth(config)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
79
|
+
if (!contentType.includes("application/x-www-form-urlencoded") &&
|
|
80
|
+
!contentType.includes("multipart/form-data")) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
let form;
|
|
84
|
+
try {
|
|
85
|
+
form = await request.formData();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (form.get("__becklyn_dp") !== "1") {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const username = String(form.get("username") ?? "");
|
|
94
|
+
const password = String(form.get("password") ?? "");
|
|
95
|
+
const returnTo = safeReturnTo(String(form.get("return_to") ?? getPathname(request)));
|
|
96
|
+
if (!(await validatePasswordCredentials(config, username, password))) {
|
|
97
|
+
return htmlResponse(renderLoginPage(config, {
|
|
98
|
+
error: "Invalid username or password.",
|
|
99
|
+
returnTo,
|
|
100
|
+
showPassword: true,
|
|
101
|
+
showVercel: hasVercelAuth(config),
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
const response = redirect(returnTo);
|
|
105
|
+
return attachSession(response, config, "password", username, isSecureRequest(request));
|
|
106
|
+
}
|
|
107
|
+
async function handleVercelAuthorize(request, config) {
|
|
108
|
+
if (getPathname(request) !== VERCEL_AUTHORIZE_PATH) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (!hasVercelAuth(config)) {
|
|
112
|
+
return new Response("Sign in with Vercel is not configured", { status: 500 });
|
|
113
|
+
}
|
|
114
|
+
const returnTo = safeReturnTo(new URL(request.url).searchParams.get("return_to"));
|
|
115
|
+
return buildVercelAuthorizeRedirect(request, config, returnTo);
|
|
116
|
+
}
|
|
117
|
+
async function handleVercelCallback(request, config) {
|
|
118
|
+
if (getPathname(request) !== VERCEL_CALLBACK_PATH) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (!hasVercelAuth(config) || !config.secret) {
|
|
122
|
+
return new Response("Sign in with Vercel is not configured", { status: 500 });
|
|
123
|
+
}
|
|
124
|
+
const url = new URL(request.url);
|
|
125
|
+
const code = url.searchParams.get("code");
|
|
126
|
+
const state = url.searchParams.get("state");
|
|
127
|
+
const secure = isSecureRequest(request);
|
|
128
|
+
const returnTo = safeReturnTo(readCookie(request, OAUTH_RETURN_COOKIE));
|
|
129
|
+
if (!code || !(await assertOAuthState(request, state))) {
|
|
130
|
+
const response = htmlResponse(renderLoginPage(config, {
|
|
131
|
+
error: "Vercel sign-in failed (invalid state).",
|
|
132
|
+
returnTo,
|
|
133
|
+
showPassword: hasPasswordAuth(config),
|
|
134
|
+
showVercel: true,
|
|
135
|
+
}));
|
|
136
|
+
clearOAuthCookies(response, secure);
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const codeVerifier = readCookie(request, OAUTH_VERIFIER_COOKIE);
|
|
141
|
+
if (!codeVerifier) {
|
|
142
|
+
throw new Error("Missing PKCE verifier");
|
|
143
|
+
}
|
|
144
|
+
const tokenData = await exchangeVercelCode(request, config, code, codeVerifier);
|
|
145
|
+
const storedNonce = readCookie(request, OAUTH_NONCE_COOKIE);
|
|
146
|
+
const tokenNonce = decodeIdTokenNonce(tokenData.id_token);
|
|
147
|
+
if (storedNonce && tokenNonce && !(await timingSafeEqualString(storedNonce, tokenNonce))) {
|
|
148
|
+
throw new Error("Nonce mismatch");
|
|
149
|
+
}
|
|
150
|
+
const user = await fetchVercelUserInfo(tokenData.access_token);
|
|
151
|
+
const subject = user.preferred_username || user.email || user.sub || "vercel-user";
|
|
152
|
+
const response = redirect(returnTo);
|
|
153
|
+
clearOAuthCookies(response, secure);
|
|
154
|
+
return attachSession(response, config, "vercel", subject, secure);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
const response = htmlResponse(renderLoginPage(config, {
|
|
158
|
+
error: "Vercel sign-in failed.",
|
|
159
|
+
returnTo,
|
|
160
|
+
showPassword: hasPasswordAuth(config),
|
|
161
|
+
showVercel: true,
|
|
162
|
+
}));
|
|
163
|
+
clearOAuthCookies(response, secure);
|
|
164
|
+
return response;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Core request handler. Returns `null` when the request may continue to the app.
|
|
169
|
+
*/
|
|
170
|
+
export async function handleDeploymentProtection(request, options = {}) {
|
|
171
|
+
const config = resolveConfig(options);
|
|
172
|
+
if (!isProtectionActive(config)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const vercelAuthorize = await handleVercelAuthorize(request, config);
|
|
176
|
+
if (vercelAuthorize) {
|
|
177
|
+
return vercelAuthorize;
|
|
178
|
+
}
|
|
179
|
+
const vercelCallback = await handleVercelCallback(request, config);
|
|
180
|
+
if (vercelCallback) {
|
|
181
|
+
return vercelCallback;
|
|
182
|
+
}
|
|
183
|
+
const passwordResponse = await handlePasswordPost(request, config);
|
|
184
|
+
if (passwordResponse) {
|
|
185
|
+
return passwordResponse;
|
|
186
|
+
}
|
|
187
|
+
const bypass = await handleBypass(request, config);
|
|
188
|
+
if (bypass.kind === "respond") {
|
|
189
|
+
return bypass.response;
|
|
190
|
+
}
|
|
191
|
+
if (bypass.kind === "allow") {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
if (config.secret) {
|
|
195
|
+
const session = await verifySessionToken(config.secret, readCookie(request, SESSION_COOKIE_NAME));
|
|
196
|
+
if (session) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!hasPasswordAuth(config) && !hasVercelAuth(config)) {
|
|
201
|
+
return new Response("Unauthorized", { status: 401 });
|
|
202
|
+
}
|
|
203
|
+
const returnTo = safeReturnTo(getPathname(request) + new URL(request.url).search);
|
|
204
|
+
return htmlResponse(renderLoginPage(config, {
|
|
205
|
+
returnTo,
|
|
206
|
+
showPassword: hasPasswordAuth(config),
|
|
207
|
+
showVercel: hasVercelAuth(config),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { deploymentProtectionMatcher, INTERNAL_PATH_PREFIX, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH, } from "./constants";
|
|
2
|
+
export { handleDeploymentProtection } from "./handler";
|
|
3
|
+
export { withDeploymentProtection } from "./middleware";
|
|
4
|
+
export { resolveConfig, hasPasswordAuth, hasVercelAuth, isProtectionActive } from "./config";
|
|
5
|
+
export type { AuthMethod, DeploymentProtectionConfig, DeploymentProtectionOptions, FormOptions, SessionPayload, } from "./types";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,2BAA2B,EAC3B,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,aAAa,CAAC;AACrB,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
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { deploymentProtectionMatcher, INTERNAL_PATH_PREFIX, VERCEL_AUTHORIZE_PATH, VERCEL_CALLBACK_PATH, } from "./constants";
|
|
2
|
+
export { handleDeploymentProtection } from "./handler";
|
|
3
|
+
export { withDeploymentProtection } from "./middleware";
|
|
4
|
+
export { resolveConfig, hasPasswordAuth, hasVercelAuth, isProtectionActive } from "./config";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DeploymentProtectionConfig } from "./types";
|
|
2
|
+
export declare function renderLoginPage(config: DeploymentProtectionConfig, options?: {
|
|
3
|
+
error?: string | null;
|
|
4
|
+
returnTo?: string;
|
|
5
|
+
showPassword?: boolean;
|
|
6
|
+
showVercel?: boolean;
|
|
7
|
+
}): string;
|
|
8
|
+
//# sourceMappingURL=login-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-page.d.ts","sourceRoot":"","sources":["../../src/login-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAW1D,wBAAgB,eAAe,CAC3B,MAAM,EAAE,0BAA0B,EAClC,OAAO,GAAE;IACL,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACnB,GACP,MAAM,CAsKR"}
|