@becklyn/deployment-protection 0.2.0 → 0.4.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.
Files changed (58) hide show
  1. package/README.md +129 -16
  2. package/dist/cjs/auth-proxy.d.ts +15 -0
  3. package/dist/cjs/auth-proxy.d.ts.map +1 -0
  4. package/dist/cjs/auth-proxy.js +135 -0
  5. package/dist/cjs/config.d.ts +7 -0
  6. package/dist/cjs/config.d.ts.map +1 -1
  7. package/dist/cjs/config.js +20 -1
  8. package/dist/cjs/constants.d.ts +1 -0
  9. package/dist/cjs/constants.d.ts.map +1 -1
  10. package/dist/cjs/constants.js +2 -1
  11. package/dist/cjs/edge.d.ts +14 -0
  12. package/dist/cjs/edge.d.ts.map +1 -0
  13. package/dist/cjs/edge.js +32 -0
  14. package/dist/cjs/handler.d.ts.map +1 -1
  15. package/dist/cjs/handler.js +38 -1
  16. package/dist/cjs/handoff.d.ts +46 -0
  17. package/dist/cjs/handoff.d.ts.map +1 -0
  18. package/dist/cjs/handoff.js +199 -0
  19. package/dist/cjs/index.d.ts +3 -1
  20. package/dist/cjs/index.d.ts.map +1 -1
  21. package/dist/cjs/index.js +11 -1
  22. package/dist/cjs/storybook.d.ts +33 -0
  23. package/dist/cjs/storybook.d.ts.map +1 -0
  24. package/dist/cjs/storybook.js +36 -0
  25. package/dist/cjs/types.d.ts +15 -0
  26. package/dist/cjs/types.d.ts.map +1 -1
  27. package/dist/cjs/vercel-oauth.d.ts +9 -2
  28. package/dist/cjs/vercel-oauth.d.ts.map +1 -1
  29. package/dist/cjs/vercel-oauth.js +8 -4
  30. package/dist/es/auth-proxy.d.ts +15 -0
  31. package/dist/es/auth-proxy.d.ts.map +1 -0
  32. package/dist/es/auth-proxy.js +130 -0
  33. package/dist/es/config.d.ts +7 -0
  34. package/dist/es/config.d.ts.map +1 -1
  35. package/dist/es/config.js +18 -1
  36. package/dist/es/constants.d.ts +1 -0
  37. package/dist/es/constants.d.ts.map +1 -1
  38. package/dist/es/constants.js +1 -0
  39. package/dist/es/edge.d.ts +14 -0
  40. package/dist/es/edge.d.ts.map +1 -0
  41. package/dist/es/edge.js +28 -0
  42. package/dist/es/handler.d.ts.map +1 -1
  43. package/dist/es/handler.js +39 -2
  44. package/dist/es/handoff.d.ts +46 -0
  45. package/dist/es/handoff.d.ts.map +1 -0
  46. package/dist/es/handoff.js +187 -0
  47. package/dist/es/index.d.ts +3 -1
  48. package/dist/es/index.d.ts.map +1 -1
  49. package/dist/es/index.js +3 -1
  50. package/dist/es/storybook.d.ts +33 -0
  51. package/dist/es/storybook.d.ts.map +1 -0
  52. package/dist/es/storybook.js +31 -0
  53. package/dist/es/types.d.ts +15 -0
  54. package/dist/es/types.d.ts.map +1 -1
  55. package/dist/es/vercel-oauth.d.ts +9 -2
  56. package/dist/es/vercel-oauth.d.ts.map +1 -1
  57. package/dist/es/vercel-oauth.js +9 -5
  58. package/package.json +19 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@becklyn/deployment-protection`
2
2
 
3
- Simple, shared deployment gate for Next.js apps on Vercel.
3
+ Simple, shared deployment gate for Next.js apps and static Storybook deploys on Vercel.
4
4
 
5
5
  Use this instead of (or after disabling) Vercel platform Deployment Protection when you need:
6
6
 
@@ -9,7 +9,10 @@ Use this instead of (or after disabling) Vercel platform Deployment Protection w
9
9
  - always-on **automation bypass** via `x-vercel-protection-bypass` / `VERCEL_AUTOMATION_BYPASS_SECRET`
10
10
  - a kill switch (`DEPLOYMENT_PROTECTION_ENABLED`, on by default)
11
11
 
12
- Protection runs in Next.js **middleware** (Next ≤15) or **proxy** (Next 16+).
12
+ Protection runs in:
13
+
14
+ - Next.js **middleware** (Next ≤15) or **proxy** (Next 16+)
15
+ - **Vercel Edge Middleware** for static Storybook (`@becklyn/deployment-protection/storybook`)
13
16
 
14
17
  > **Note:** Vercel Connect is unrelated (third-party API tokens). Platform Vercel Authentication cookies are not visible to your app once platform protection is off — team members use the **Sign in with Vercel** button instead.
15
18
 
@@ -21,7 +24,45 @@ npm i @becklyn/deployment-protection
21
24
 
22
25
  ## Quick setup
23
26
 
24
- ### 1. Middleware / proxy
27
+ ### Storybook (static on Vercel)
28
+
29
+ Built Storybook is static (`storybook-static`), so protection is **Vercel Edge Middleware** — not a Storybook addon.
30
+
31
+ 1. Install the package in the project that deploys Storybook.
32
+ 2. Add `middleware.ts` at the **Vercel project root** (same level Vercel uses for `vercel.json` / output):
33
+
34
+ ```ts
35
+ import { withStorybookDeploymentProtection } from "@becklyn/deployment-protection/storybook";
36
+
37
+ export default withStorybookDeploymentProtection();
38
+
39
+ export const config = {
40
+ matcher: ["/((?!.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|map|txt|xml|woff2?|mjs)$).*)"],
41
+ };
42
+ ```
43
+
44
+ 3. Set the same environment variables as for Next.js (below).
45
+ 4. Optional `vercel.json` for a Storybook-only project:
46
+
47
+ ```json
48
+ {
49
+ "buildCommand": "npm run build-storybook",
50
+ "outputDirectory": "storybook-static",
51
+ "framework": null
52
+ }
53
+ ```
54
+
55
+ `withStorybookDeploymentProtection` does **not** require the `next` package. The matcher must stay a **string literal** in `middleware.ts` (same static-analysis rule as Next.js).
56
+
57
+ Framework-agnostic alias (same implementation):
58
+
59
+ ```ts
60
+ import { withEdgeDeploymentProtection } from "@becklyn/deployment-protection/edge";
61
+
62
+ export default withEdgeDeploymentProtection();
63
+ ```
64
+
65
+ ### 1. Next.js middleware / proxy
25
66
 
26
67
  Next.js requires `config.matcher` to be a **string literal in the local middleware/proxy file**.
27
68
  It cannot be imported from a package (Next analyzes the matcher statically at build time).
@@ -70,33 +111,97 @@ export default withDeploymentProtection(async request => {
70
111
  | `DEPLOYMENT_PROTECTION_ENABLED` | no | `true` | Set `false` / `0` / `off` to disable |
71
112
  | `DEPLOYMENT_PROTECTION_USERNAME` | for password auth | — | Shared username |
72
113
  | `DEPLOYMENT_PROTECTION_PASSWORD` | for password auth | — | Shared password |
73
- | `DEPLOYMENT_PROTECTION_SECRET` | recommended | derived from user+pass | HMAC secret for session cookies |
114
+ | `DEPLOYMENT_PROTECTION_SECRET` | recommended | derived from user+pass | HMAC secret for session cookies (+ handoff fallback) |
115
+ | `DEPLOYMENT_PROTECTION_AUTH_PROXY_URL` | for proxy SSO | — | Base URL of the central auth-proxy app |
116
+ | `DEPLOYMENT_PROTECTION_HANDOFF_SECRET` | no | same as `…_SECRET` | Shared secret between apps and the auth-proxy |
74
117
  | `VERCEL_AUTOMATION_BYPASS_SECRET` | no | — | Same secret as Vercel Protection Bypass for Automation |
75
- | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_ID` | for Vercel login | — | Or `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` |
76
- | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_SECRET` | for Vercel login | — | Or `VERCEL_APP_CLIENT_SECRET` |
118
+ | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_ID` | direct Vercel SSO | — | Or `NEXT_PUBLIC_VERCEL_APP_CLIENT_ID` (legacy) |
119
+ | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_SECRET` | direct Vercel SSO | — | Or `VERCEL_APP_CLIENT_SECRET` (legacy) |
77
120
  | `DEPLOYMENT_PROTECTION_FORM_TITLE` | no | `Authentication required` | Login heading |
78
121
  | `DEPLOYMENT_PROTECTION_FORM_DESCRIPTION` | no | … | Login description |
79
122
 
80
- At least one of password auth, Vercel OAuth, or a bypass secret must be configured while enabled. If nothing is configured, the gate stays inactive (fail-open) so local dev is not bricked.
123
+ At least one of password auth, Vercel OAuth (proxy or direct), or a bypass secret must be configured while enabled. If nothing is configured, the gate stays inactive (fail-open) so local dev is not bricked.
124
+
125
+ ### 3. Sign in with Vercel via auth-proxy (recommended)
126
+
127
+ Register **one** OAuth callback URL on a shared [Sign in with Vercel](https://vercel.com/docs/sign-in-with-vercel) app:
128
+
129
+ ```text
130
+ https://<auth-proxy-host>/callback
131
+ ```
132
+
133
+ Deploy the `deployment-protection-auth-proxy` app from this monorepo (or any tiny Next app that wires the handlers below) and set:
134
+
135
+ **On the auth-proxy**
136
+
137
+ | Variable | Description |
138
+ | ---------------------------------------------------- | -------------------------------------- |
139
+ | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_ID` | Vercel OAuth client id |
140
+ | `DEPLOYMENT_PROTECTION_VERCEL_CLIENT_SECRET` | Vercel OAuth client secret |
141
+ | `DEPLOYMENT_PROTECTION_SECRET` or `…_HANDOFF_SECRET` | Shared HMAC secret with protected apps |
142
+ | `DEPLOYMENT_PROTECTION_ALLOWED_ORIGINS` | Optional allowlist (see below) |
143
+
144
+ **On every protected app**
145
+
146
+ | Variable | Description |
147
+ | -------------------------------------- | -------------------------------------- |
148
+ | `DEPLOYMENT_PROTECTION_AUTH_PROXY_URL` | e.g. `https://dp-auth.example.com` |
149
+ | `DEPLOYMENT_PROTECTION_SECRET` | Session cookie secret |
150
+ | `DEPLOYMENT_PROTECTION_HANDOFF_SECRET` | Same as proxy (defaults to `…_SECRET`) |
151
+
152
+ No per-project / per-preview callback URLs.
153
+
154
+ #### Flow
155
+
156
+ 1. App redirects to `{AUTH_PROXY_URL}/start` with a **signed** `return_origin` + `return_path`.
157
+ 2. Proxy runs Vercel OAuth against its fixed `/callback`.
158
+ 3. Proxy redirects to
159
+ `{return_origin}/_becklyn/deployment-protection/vercel/callback?handoff=…&return_to=…`
160
+ 4. App verifies the short-lived handoff token (`aud` must match its origin), sets `__becklyn_dp_session`, continues.
161
+
162
+ #### Allowlist (proxy)
163
+
164
+ `DEPLOYMENT_PROTECTION_ALLOWED_ORIGINS` is a comma-separated list:
165
+
166
+ - full origins: `https://app.example.com`
167
+ - host suffixes: `.vercel.app`, `example.com`
168
+ - `localhost` (http(s) localhost / 127.0.0.1)
169
+
170
+ Empty allowlist → any origin that passes https (or local http) validation. Prefer an allowlist in production.
171
+
172
+ #### Minimal proxy route handlers
173
+
174
+ ```ts
175
+ // app/start/route.ts
176
+ import { handleAuthProxyStart } from "@becklyn/deployment-protection";
177
+
178
+ export const GET = (request: Request) => handleAuthProxyStart(request);
179
+
180
+ // app/callback/route.ts
181
+ import { handleAuthProxyCallback } from "@becklyn/deployment-protection";
182
+
183
+ export const GET = (request: Request) => handleAuthProxyCallback(request);
184
+ ```
185
+
186
+ ### 4. Sign in with Vercel (legacy direct mode)
81
187
 
82
- ### 3. Sign in with Vercel (optional)
188
+ Still supported when `DEPLOYMENT_PROTECTION_AUTH_PROXY_URL` is unset:
83
189
 
84
- 1. Create a [Sign in with Vercel](https://vercel.com/docs/sign-in-with-vercel) app in the Vercel dashboard.
85
- 2. Generate a client secret.
86
- 3. Add authorization callback URLs for each project host, pointing at:
190
+ 1. Create a Sign in with Vercel app.
191
+ 2. Add authorization callback URLs for **each** project host:
87
192
 
88
193
  ```text
89
194
  https://<your-host>/_becklyn/deployment-protection/vercel/callback
90
195
  http://localhost:3000/_becklyn/deployment-protection/vercel/callback
91
196
  ```
92
197
 
93
- You can attach the path to preview + production domains of each project from the dashboard.
198
+ 3. Set client id/secret on the project(s).
94
199
 
95
- 4. Set client id/secret env vars on the project(s). Prefer team-level shared env values.
200
+ When both proxy URL and direct client credentials are set, **proxy mode wins** for the authorize step.
96
201
 
97
202
  Scopes used: `openid email profile`.
98
203
 
99
- ### 4. Automation bypass
204
+ ### 5. Automation bypass
100
205
 
101
206
  Matches Vercel’s Protection Bypass for Automation:
102
207
 
@@ -116,7 +221,7 @@ When platform Deployment Protection is disabled, **this package** enforces the b
116
221
  ## Behaviour
117
222
 
118
223
  1. Disabled / misconfigured → pass through
119
- 2. Internal Vercel OAuth routes → handled by the package
224
+ 2. Internal Vercel OAuth routes → handled by the package (proxy start or direct OAuth / handoff)
120
225
  3. Login form `POST` → validate username/password, set signed HttpOnly session cookie
121
226
  4. Valid bypass header/query/cookie → allow (optionally set cookies)
122
227
  5. Valid session cookie → allow
@@ -136,14 +241,20 @@ Redeploy. Middleware stays installed but is a no-op.
136
241
 
137
242
  ```ts
138
243
  import {
244
+ handleAuthProxyCallback,
245
+ handleAuthProxyStart,
139
246
  handleDeploymentProtection,
140
247
  resolveConfig,
141
248
  withDeploymentProtection,
249
+ withEdgeDeploymentProtection,
142
250
  } from "@becklyn/deployment-protection";
251
+ import { withStorybookDeploymentProtection } from "@becklyn/deployment-protection/storybook";
143
252
  ```
144
253
 
145
- - `withDeploymentProtection(options?)` / `withDeploymentProtection(next, options?)` — middleware/proxy factory
254
+ - `withDeploymentProtection(options?)` / `withDeploymentProtection(next, options?)` — Next.js middleware/proxy factory
255
+ - `withStorybookDeploymentProtection(options?)` / `withEdgeDeploymentProtection(options?)` — Vercel Edge Middleware for Storybook / static deploys (no Next.js)
146
256
  - `handleDeploymentProtection(request, options?)` — framework-agnostic core (`null` = continue)
257
+ - `handleAuthProxyStart` / `handleAuthProxyCallback` — central auth-proxy routes
147
258
 
148
259
  Recommended matcher (must be pasted as a string literal in your middleware/proxy file — see above):
149
260
 
@@ -155,6 +266,8 @@ Recommended matcher (must be pasted as a string literal in your middleware/proxy
155
266
 
156
267
  - This is a **shared-secret gate**, not per-user product auth.
157
268
  - Prefer a dedicated `DEPLOYMENT_PROTECTION_SECRET` in production.
269
+ - Auth-proxy start requests are HMAC-signed; handoff tokens are short-lived and audience-bound.
270
+ - Do not put the raw handoff secret in query strings — only signatures/tokens.
158
271
  - Turn off paid Vercel Password Protection / seat-heavy sharing once this is live; keep `VERCEL_AUTOMATION_BYPASS_SECRET` configured for tooling.
159
272
  - Middleware is a convenience edge check — do not treat it as the only control for highly sensitive data.
160
273
 
@@ -0,0 +1,15 @@
1
+ import type { DeploymentProtectionOptions } from "./types";
2
+ /** Fixed OAuth callback path registered once on the Vercel OAuth app. */
3
+ export declare const AUTH_PROXY_CALLBACK_PATH = "/callback";
4
+ export declare const AUTH_PROXY_START_PATH = "/start";
5
+ /**
6
+ * Auth-proxy entry: verify the signed start request from a protected app, then
7
+ * begin Sign in with Vercel against this proxy's fixed callback URL.
8
+ */
9
+ export declare function handleAuthProxyStart(request: Request, options?: DeploymentProtectionOptions): Promise<Response>;
10
+ /**
11
+ * Auth-proxy Vercel OAuth callback: exchange the code, mint a short-lived handoff
12
+ * token, and redirect back to the protected app callback.
13
+ */
14
+ export declare function handleAuthProxyCallback(request: Request, options?: DeploymentProtectionOptions): Promise<Response>;
15
+ //# sourceMappingURL=auth-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-proxy.d.ts","sourceRoot":"","sources":["../../src/auth-proxy.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAY3D,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,cAAc,CAAC;AACpD,eAAO,MAAM,qBAAqB,WAAW,CAAC;AA0B9C;;;GAGG;AACH,wBAAsB,oBAAoB,CACtC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,CAAC,CAwCnB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,CAAC,CAqEnB"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_PROXY_START_PATH = exports.AUTH_PROXY_CALLBACK_PATH = void 0;
4
+ exports.handleAuthProxyStart = handleAuthProxyStart;
5
+ exports.handleAuthProxyCallback = handleAuthProxyCallback;
6
+ const config_1 = require("./config");
7
+ const constants_1 = require("./constants");
8
+ const crypto_1 = require("./crypto");
9
+ const handoff_1 = require("./handoff");
10
+ const safe_return_to_1 = require("./safe-return-to");
11
+ const vercel_oauth_1 = require("./vercel-oauth");
12
+ /** Fixed OAuth callback path registered once on the Vercel OAuth app. */
13
+ exports.AUTH_PROXY_CALLBACK_PATH = "/callback";
14
+ exports.AUTH_PROXY_START_PATH = "/start";
15
+ function isSecureRequest(request) {
16
+ return new URL(request.url).protocol === "https:";
17
+ }
18
+ function textResponse(message, status) {
19
+ return new Response(message, {
20
+ status,
21
+ headers: {
22
+ "Content-Type": "text/plain; charset=utf-8",
23
+ "Cache-Control": "no-store",
24
+ },
25
+ });
26
+ }
27
+ function cookieOptions(secure) {
28
+ return {
29
+ httpOnly: true,
30
+ sameSite: "lax",
31
+ secure,
32
+ path: "/",
33
+ maxAge: 10 * 60,
34
+ };
35
+ }
36
+ /**
37
+ * Auth-proxy entry: verify the signed start request from a protected app, then
38
+ * begin Sign in with Vercel against this proxy's fixed callback URL.
39
+ */
40
+ async function handleAuthProxyStart(request, options = {}) {
41
+ const config = (0, config_1.resolveConfig)(options);
42
+ if (!(0, config_1.hasVercelDirectAuth)(config) || !config.handoffSecret) {
43
+ return textResponse("Auth proxy is not configured", 500);
44
+ }
45
+ const url = new URL(request.url);
46
+ const returnOrigin = (0, handoff_1.normalizeReturnOrigin)(url.searchParams.get("return_origin"));
47
+ const returnPath = (0, safe_return_to_1.safeReturnTo)(url.searchParams.get("return_path"));
48
+ const expRaw = url.searchParams.get("exp");
49
+ const nonce = url.searchParams.get("nonce");
50
+ const sig = url.searchParams.get("sig");
51
+ const exp = expRaw ? Number(expRaw) : NaN;
52
+ if (!returnOrigin || !nonce || !Number.isFinite(exp)) {
53
+ return textResponse("Invalid start request", 400);
54
+ }
55
+ if (!(0, handoff_1.isReturnOriginAllowed)(returnOrigin, config.allowedReturnOrigins)) {
56
+ return textResponse("Return origin is not allowed", 403);
57
+ }
58
+ const params = {
59
+ returnOrigin,
60
+ returnPath,
61
+ exp,
62
+ nonce,
63
+ };
64
+ if (!(await (0, handoff_1.verifyProxyStartSignature)(config.handoffSecret, params, sig))) {
65
+ return textResponse("Invalid or expired start signature", 403);
66
+ }
67
+ const response = await (0, vercel_oauth_1.buildVercelAuthorizeRedirect)(request, config, returnPath, {
68
+ callbackPath: exports.AUTH_PROXY_CALLBACK_PATH,
69
+ });
70
+ const secure = isSecureRequest(request);
71
+ (0, vercel_oauth_1.appendSetCookie)(response, constants_1.OAUTH_RETURN_ORIGIN_COOKIE, returnOrigin, cookieOptions(secure));
72
+ return response;
73
+ }
74
+ /**
75
+ * Auth-proxy Vercel OAuth callback: exchange the code, mint a short-lived handoff
76
+ * token, and redirect back to the protected app callback.
77
+ */
78
+ async function handleAuthProxyCallback(request, options = {}) {
79
+ const config = (0, config_1.resolveConfig)(options);
80
+ if (!(0, config_1.hasVercelDirectAuth)(config) || !config.handoffSecret) {
81
+ return textResponse("Auth proxy is not configured", 500);
82
+ }
83
+ const url = new URL(request.url);
84
+ const code = url.searchParams.get("code");
85
+ const state = url.searchParams.get("state");
86
+ const secure = isSecureRequest(request);
87
+ const returnOrigin = (0, handoff_1.normalizeReturnOrigin)((0, vercel_oauth_1.readCookie)(request, constants_1.OAUTH_RETURN_ORIGIN_COOKIE));
88
+ const returnPath = (0, safe_return_to_1.safeReturnTo)((0, vercel_oauth_1.readCookie)(request, constants_1.OAUTH_RETURN_COOKIE));
89
+ const fail = (message, status = 401) => {
90
+ const response = textResponse(message, status);
91
+ (0, vercel_oauth_1.clearOAuthCookies)(response, secure);
92
+ return response;
93
+ };
94
+ if (!returnOrigin) {
95
+ return fail("Missing return origin", 400);
96
+ }
97
+ if (!(0, handoff_1.isReturnOriginAllowed)(returnOrigin, config.allowedReturnOrigins)) {
98
+ return fail("Return origin is not allowed", 403);
99
+ }
100
+ if (!code || !(await (0, vercel_oauth_1.assertOAuthState)(request, state))) {
101
+ return fail("Vercel sign-in failed (invalid state)");
102
+ }
103
+ try {
104
+ const codeVerifier = (0, vercel_oauth_1.readCookie)(request, constants_1.OAUTH_VERIFIER_COOKIE);
105
+ if (!codeVerifier) {
106
+ throw new Error("Missing PKCE verifier");
107
+ }
108
+ const tokenData = await (0, vercel_oauth_1.exchangeVercelCode)(request, config, code, codeVerifier, {
109
+ callbackPath: exports.AUTH_PROXY_CALLBACK_PATH,
110
+ });
111
+ const storedNonce = (0, vercel_oauth_1.readCookie)(request, constants_1.OAUTH_NONCE_COOKIE);
112
+ const tokenNonce = (0, vercel_oauth_1.decodeIdTokenNonce)(tokenData.id_token);
113
+ if (storedNonce && tokenNonce && !(await (0, crypto_1.timingSafeEqualString)(storedNonce, tokenNonce))) {
114
+ throw new Error("Nonce mismatch");
115
+ }
116
+ const user = await (0, vercel_oauth_1.fetchVercelUserInfo)(tokenData.access_token);
117
+ const subject = user.preferred_username || user.email || user.sub || "vercel-user";
118
+ const handoff = await (0, handoff_1.createHandoffToken)(config.handoffSecret, returnOrigin, subject);
119
+ const target = new URL(constants_1.VERCEL_CALLBACK_PATH, `${returnOrigin}/`);
120
+ target.searchParams.set("handoff", handoff);
121
+ target.searchParams.set("return_to", returnPath);
122
+ const response = new Response(null, {
123
+ status: 302,
124
+ headers: {
125
+ Location: target.toString(),
126
+ "Cache-Control": "no-store",
127
+ },
128
+ });
129
+ (0, vercel_oauth_1.clearOAuthCookies)(response, secure);
130
+ return response;
131
+ }
132
+ catch {
133
+ return fail("Vercel sign-in failed");
134
+ }
135
+ }
@@ -4,6 +4,13 @@ import type { DeploymentProtectionConfig, DeploymentProtectionOptions } from "./
4
4
  */
5
5
  export declare function resolveConfig(options?: DeploymentProtectionOptions): DeploymentProtectionConfig;
6
6
  export declare function hasPasswordAuth(config: DeploymentProtectionConfig): boolean;
7
+ /** Direct Vercel OAuth on the protected app (legacy / single-project setup). */
8
+ export declare function hasVercelDirectAuth(config: DeploymentProtectionConfig): boolean;
9
+ /**
10
+ * Central auth-proxy mode: apps only need the proxy URL + shared handoff secret.
11
+ * Vercel client credentials live solely on the proxy.
12
+ */
13
+ export declare function hasVercelProxyAuth(config: DeploymentProtectionConfig): boolean;
7
14
  export declare function hasVercelAuth(config: DeploymentProtectionConfig): boolean;
8
15
  /**
9
16
  * Protection only runs when enabled and at least one auth method is configured.
@@ -1 +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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,2BAA2B,EAAU,MAAM,SAAS,CAAC;AA0B/F;;GAEG;AACH,wBAAgB,aAAa,CACzB,OAAO,GAAE,2BAAgC,GAC1C,0BAA0B,CA0D5B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAE3E;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAE/E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAE9E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAEzE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAK9E"}
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveConfig = resolveConfig;
4
4
  exports.hasPasswordAuth = hasPasswordAuth;
5
+ exports.hasVercelDirectAuth = hasVercelDirectAuth;
6
+ exports.hasVercelProxyAuth = hasVercelProxyAuth;
5
7
  exports.hasVercelAuth = hasVercelAuth;
6
8
  exports.isProtectionActive = isProtectionActive;
9
+ const handoff_1 = require("./handoff");
7
10
  const FALSEY = new Set(["0", "false", "no", "off"]);
8
11
  function read(env, ...keys) {
9
12
  for (const key of keys) {
@@ -35,12 +38,17 @@ function resolveConfig(options = {}) {
35
38
  // Prefer an explicit secret; otherwise derive a stable key from credentials so
36
39
  // projects can stay zero-config beyond username/password.
37
40
  const secret = explicitSecret ?? (username && password ? `dp:${username}:${password}` : null);
41
+ const handoffSecret = read(env, "DEPLOYMENT_PROTECTION_HANDOFF_SECRET") ?? explicitSecret ?? secret;
42
+ const authProxyUrl = read(env, "DEPLOYMENT_PROTECTION_AUTH_PROXY_URL")?.replace(/\/$/, "") ?? null;
38
43
  return {
39
44
  enabled: isEnabled(env),
40
45
  username,
41
46
  password,
42
47
  secret,
48
+ handoffSecret,
43
49
  bypassSecret: read(env, "VERCEL_AUTOMATION_BYPASS_SECRET", "DEPLOYMENT_PROTECTION_BYPASS_SECRET"),
50
+ authProxyUrl,
51
+ allowedReturnOrigins: (0, handoff_1.parseAllowlist)(read(env, "DEPLOYMENT_PROTECTION_ALLOWED_ORIGINS", "DEPLOYMENT_PROTECTION_AUTH_PROXY_ALLOWED_ORIGINS")),
44
52
  vercelClientId: read(env, "DEPLOYMENT_PROTECTION_VERCEL_CLIENT_ID", "NEXT_PUBLIC_VERCEL_APP_CLIENT_ID", "VERCEL_APP_CLIENT_ID"),
45
53
  vercelClientSecret: read(env, "DEPLOYMENT_PROTECTION_VERCEL_CLIENT_SECRET", "VERCEL_APP_CLIENT_SECRET"),
46
54
  sessionTtlSeconds: options.sessionTtlSeconds ?? 60 * 60 * 24 * 14,
@@ -55,9 +63,20 @@ function resolveConfig(options = {}) {
55
63
  function hasPasswordAuth(config) {
56
64
  return Boolean(config.username && config.password && config.secret);
57
65
  }
58
- function hasVercelAuth(config) {
66
+ /** Direct Vercel OAuth on the protected app (legacy / single-project setup). */
67
+ function hasVercelDirectAuth(config) {
59
68
  return Boolean(config.vercelClientId && config.vercelClientSecret && config.secret);
60
69
  }
70
+ /**
71
+ * Central auth-proxy mode: apps only need the proxy URL + shared handoff secret.
72
+ * Vercel client credentials live solely on the proxy.
73
+ */
74
+ function hasVercelProxyAuth(config) {
75
+ return Boolean(config.authProxyUrl && config.handoffSecret && config.secret);
76
+ }
77
+ function hasVercelAuth(config) {
78
+ return hasVercelDirectAuth(config) || hasVercelProxyAuth(config);
79
+ }
61
80
  /**
62
81
  * Protection only runs when enabled and at least one auth method is configured.
63
82
  * Misconfigured+enabled is treated as inactive (fail-open) so local/dev isn't bricked.
@@ -4,6 +4,7 @@ export declare const OAUTH_STATE_COOKIE = "__becklyn_dp_oauth_state";
4
4
  export declare const OAUTH_NONCE_COOKIE = "__becklyn_dp_oauth_nonce";
5
5
  export declare const OAUTH_VERIFIER_COOKIE = "__becklyn_dp_oauth_verifier";
6
6
  export declare const OAUTH_RETURN_COOKIE = "__becklyn_dp_oauth_return";
7
+ export declare const OAUTH_RETURN_ORIGIN_COOKIE = "__becklyn_dp_oauth_return_origin";
7
8
  export declare const INTERNAL_PATH_PREFIX = "/_becklyn/deployment-protection";
8
9
  export declare const VERCEL_AUTHORIZE_PATH = "/_becklyn/deployment-protection/vercel";
9
10
  export declare const VERCEL_CALLBACK_PATH = "/_becklyn/deployment-protection/vercel/callback";
@@ -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;AAC/D,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAE7E,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,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
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_ORIGIN_COOKIE = 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";
7
7
  exports.OAUTH_NONCE_COOKIE = "__becklyn_dp_oauth_nonce";
8
8
  exports.OAUTH_VERIFIER_COOKIE = "__becklyn_dp_oauth_verifier";
9
9
  exports.OAUTH_RETURN_COOKIE = "__becklyn_dp_oauth_return";
10
+ exports.OAUTH_RETURN_ORIGIN_COOKIE = "__becklyn_dp_oauth_return_origin";
10
11
  exports.INTERNAL_PATH_PREFIX = "/_becklyn/deployment-protection";
11
12
  exports.VERCEL_AUTHORIZE_PATH = `${exports.INTERNAL_PATH_PREFIX}/vercel`;
12
13
  exports.VERCEL_CALLBACK_PATH = `${exports.INTERNAL_PATH_PREFIX}/vercel/callback`;
@@ -0,0 +1,14 @@
1
+ import type { DeploymentProtectionOptions } from "./types";
2
+ /**
3
+ * Tell Vercel Edge Middleware to continue to the upstream / static asset.
4
+ * Same signal as `NextResponse.next()` without depending on the Next.js runtime.
5
+ */
6
+ export declare function middlewarePassThrough(): Response;
7
+ /**
8
+ * Framework-agnostic Vercel Edge Middleware factory (no Next.js runtime).
9
+ *
10
+ * Intended for static deployments such as Storybook `storybook-static` on Vercel.
11
+ * Reuses the same env vars and auth behaviour as {@link withDeploymentProtection}.
12
+ */
13
+ export declare function withEdgeDeploymentProtection(options?: DeploymentProtectionOptions): (request: Request) => Promise<Response>;
14
+ //# sourceMappingURL=edge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge.d.ts","sourceRoot":"","sources":["../../src/edge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAOhD;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,2BAAgC,IACzB,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAShG"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.middlewarePassThrough = middlewarePassThrough;
4
+ exports.withEdgeDeploymentProtection = withEdgeDeploymentProtection;
5
+ const handler_1 = require("./handler");
6
+ /**
7
+ * Tell Vercel Edge Middleware to continue to the upstream / static asset.
8
+ * Same signal as `NextResponse.next()` without depending on the Next.js runtime.
9
+ */
10
+ function middlewarePassThrough() {
11
+ return new Response(null, {
12
+ status: 200,
13
+ headers: {
14
+ "x-middleware-next": "1",
15
+ },
16
+ });
17
+ }
18
+ /**
19
+ * Framework-agnostic Vercel Edge Middleware factory (no Next.js runtime).
20
+ *
21
+ * Intended for static deployments such as Storybook `storybook-static` on Vercel.
22
+ * Reuses the same env vars and auth behaviour as {@link withDeploymentProtection}.
23
+ */
24
+ function withEdgeDeploymentProtection(options = {}) {
25
+ return async function deploymentProtectionEdgeMiddleware(request) {
26
+ const protectionResponse = await (0, handler_1.handleDeploymentProtection)(request, options);
27
+ if (protectionResponse) {
28
+ return protectionResponse;
29
+ }
30
+ return middlewarePassThrough();
31
+ };
32
+ }
@@ -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;AA6OvF;;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":"AAyBA,OAAO,KAAK,EAA8B,2BAA2B,EAAE,MAAM,SAAS,CAAC;AA4RvF;;GAEG;AACH,wBAAsB,0BAA0B,CAC5C,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqD1B"}
@@ -5,6 +5,7 @@ const bypass_1 = require("./bypass");
5
5
  const config_1 = require("./config");
6
6
  const constants_1 = require("./constants");
7
7
  const crypto_1 = require("./crypto");
8
+ const handoff_1 = require("./handoff");
8
9
  const login_page_1 = require("./login-page");
9
10
  const password_1 = require("./password");
10
11
  const safe_return_to_1 = require("./safe-return-to");
@@ -117,6 +118,19 @@ async function handleVercelAuthorize(request, config) {
117
118
  return new Response("Sign in with Vercel is not configured", { status: 500 });
118
119
  }
119
120
  const returnTo = (0, safe_return_to_1.safeReturnTo)(new URL(request.url).searchParams.get("return_to"));
121
+ // Prefer central auth-proxy so apps never register per-host OAuth callbacks.
122
+ if ((0, config_1.hasVercelProxyAuth)(config) && config.authProxyUrl && config.handoffSecret) {
123
+ const startUrl = await (0, handoff_1.buildProxyStartUrl)({
124
+ authProxyUrl: config.authProxyUrl,
125
+ secret: config.handoffSecret,
126
+ returnOrigin: new URL(request.url).origin,
127
+ returnPath: returnTo,
128
+ });
129
+ return redirect(request, startUrl);
130
+ }
131
+ if (!(0, config_1.hasVercelDirectAuth)(config)) {
132
+ return new Response("Sign in with Vercel is not configured", { status: 500 });
133
+ }
120
134
  return (0, vercel_oauth_1.buildVercelAuthorizeRedirect)(request, config, returnTo);
121
135
  }
122
136
  async function handleVercelCallback(request, config) {
@@ -127,9 +141,32 @@ async function handleVercelCallback(request, config) {
127
141
  return new Response("Sign in with Vercel is not configured", { status: 500 });
128
142
  }
129
143
  const url = new URL(request.url);
144
+ const handoff = url.searchParams.get("handoff");
145
+ const secure = isSecureRequest(request);
146
+ // Auth-proxy handoff: short-lived signed token, no Vercel client secret on the app.
147
+ if (handoff) {
148
+ if (!config.handoffSecret) {
149
+ return new Response("Sign in with Vercel is not configured", { status: 500 });
150
+ }
151
+ const returnTo = (0, safe_return_to_1.safeReturnTo)(url.searchParams.get("return_to"));
152
+ const payload = await (0, handoff_1.verifyHandoffToken)(config.handoffSecret, handoff, url.origin);
153
+ if (!payload) {
154
+ return htmlResponse((0, login_page_1.renderLoginPage)(config, {
155
+ error: "Vercel sign-in failed (invalid handoff).",
156
+ returnTo,
157
+ showPassword: (0, config_1.hasPasswordAuth)(config),
158
+ showVercel: true,
159
+ }));
160
+ }
161
+ const response = redirect(request, returnTo);
162
+ return attachSession(response, config, "vercel", payload.subject, secure);
163
+ }
164
+ // Legacy direct OAuth callback on the protected app.
165
+ if (!(0, config_1.hasVercelDirectAuth)(config)) {
166
+ return new Response("Sign in with Vercel is not configured", { status: 500 });
167
+ }
130
168
  const code = url.searchParams.get("code");
131
169
  const state = url.searchParams.get("state");
132
- const secure = isSecureRequest(request);
133
170
  const returnTo = (0, safe_return_to_1.safeReturnTo)((0, vercel_oauth_1.readCookie)(request, constants_1.OAUTH_RETURN_COOKIE));
134
171
  if (!code || !(await (0, vercel_oauth_1.assertOAuthState)(request, state))) {
135
172
  const response = htmlResponse((0, login_page_1.renderLoginPage)(config, {
@@ -0,0 +1,46 @@
1
+ export declare const HANDOFF_TTL_SECONDS = 60;
2
+ export declare const PROXY_START_TTL_SECONDS: number;
3
+ export interface ProxyStartParams {
4
+ returnOrigin: string;
5
+ returnPath: string;
6
+ exp: number;
7
+ nonce: string;
8
+ }
9
+ export interface HandoffPayload {
10
+ exp: number;
11
+ aud: string;
12
+ subject: string;
13
+ method: "vercel";
14
+ }
15
+ /**
16
+ * Canonical payload for HMAC over the proxy start request.
17
+ * Field order is fixed so app and proxy produce the same signature.
18
+ */
19
+ export declare function canonicalProxyStartPayload(params: ProxyStartParams): string;
20
+ export declare function signProxyStart(secret: string, params: ProxyStartParams): Promise<string>;
21
+ export declare function verifyProxyStartSignature(secret: string, params: ProxyStartParams, signature: string | null | undefined): Promise<boolean>;
22
+ export declare function buildProxyStartUrl(options: {
23
+ authProxyUrl: string;
24
+ secret: string;
25
+ returnOrigin: string;
26
+ returnPath: string;
27
+ ttlSeconds?: number;
28
+ }): Promise<string>;
29
+ export declare function createHandoffToken(secret: string, audienceOrigin: string, subject: string, ttlSeconds?: number): Promise<string>;
30
+ export declare function verifyHandoffToken(secret: string, token: string | null | undefined, expectedAudienceOrigin: string): Promise<HandoffPayload | null>;
31
+ /**
32
+ * Validate a post-login return origin for the auth proxy.
33
+ * Only https (and http://localhost / 127.0.0.1) are accepted.
34
+ */
35
+ export declare function normalizeReturnOrigin(value: string | null | undefined): string | null;
36
+ /**
37
+ * Optional allowlist. Entries may be:
38
+ * - full origins (`https://app.example.com`)
39
+ * - hostname suffixes (`.vercel.app`, `example.com`)
40
+ * - `localhost` (any localhost / 127.0.0.1 http(s) origin)
41
+ *
42
+ * Empty allowlist → any origin that passes {@link normalizeReturnOrigin}.
43
+ */
44
+ export declare function isReturnOriginAllowed(origin: string, allowlist: string[]): boolean;
45
+ export declare function parseAllowlist(value: string | null | undefined): string[];
46
+ //# sourceMappingURL=handoff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../../src/handoff.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC;CACpB;AAeD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE3E;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAE9F;AAED,wBAAsB,yBAAyB,CAC3C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBlB;AAWD,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,SAAsB,GACjC,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,sBAAsB,EAAE,MAAM,GAC/B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAwChC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CA4BrF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAgDlF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CASzE"}