alchemy 0.93.4 → 0.93.7
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/bin/alchemy.js +1966 -1825
- package/bin/alchemy.ts +2 -0
- package/bin/commands/state.ts +58 -0
- package/bin/services/execute-alchemy.ts +9 -1
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +7 -0
- package/lib/alchemy.js.map +1 -1
- package/lib/cloudflare/access-application.d.ts +204 -0
- package/lib/cloudflare/access-application.d.ts.map +1 -0
- package/lib/cloudflare/access-application.js +205 -0
- package/lib/cloudflare/access-application.js.map +1 -0
- package/lib/cloudflare/access-group.d.ts +115 -0
- package/lib/cloudflare/access-group.d.ts.map +1 -0
- package/lib/cloudflare/access-group.js +147 -0
- package/lib/cloudflare/access-group.js.map +1 -0
- package/lib/cloudflare/access-identity-provider.d.ts +261 -0
- package/lib/cloudflare/access-identity-provider.d.ts.map +1 -0
- package/lib/cloudflare/access-identity-provider.js +229 -0
- package/lib/cloudflare/access-identity-provider.js.map +1 -0
- package/lib/cloudflare/access-policy.d.ts +168 -0
- package/lib/cloudflare/access-policy.d.ts.map +1 -0
- package/lib/cloudflare/access-policy.js +176 -0
- package/lib/cloudflare/access-policy.js.map +1 -0
- package/lib/cloudflare/access-rule.d.ts +248 -0
- package/lib/cloudflare/access-rule.d.ts.map +1 -0
- package/lib/cloudflare/access-rule.js +37 -0
- package/lib/cloudflare/access-rule.js.map +1 -0
- package/lib/cloudflare/access-service-token.d.ts +86 -0
- package/lib/cloudflare/access-service-token.d.ts.map +1 -0
- package/lib/cloudflare/access-service-token.js +127 -0
- package/lib/cloudflare/access-service-token.js.map +1 -0
- package/lib/cloudflare/index.d.ts +6 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +6 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +17 -3
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/state/file-system-state-store.d.ts +1 -0
- package/lib/state/file-system-state-store.d.ts.map +1 -1
- package/lib/state/file-system-state-store.js +16 -0
- package/lib/state/file-system-state-store.js.map +1 -1
- package/lib/state/operations.d.ts +1 -0
- package/lib/state/operations.d.ts.map +1 -1
- package/lib/state/operations.js +24 -0
- package/lib/state/operations.js.map +1 -1
- package/lib/state/proxy.d.ts +1 -0
- package/lib/state/proxy.d.ts.map +1 -1
- package/lib/state/proxy.js +3 -0
- package/lib/state/proxy.js.map +1 -1
- package/lib/state-cli.d.ts +9 -0
- package/lib/state-cli.d.ts.map +1 -0
- package/lib/state-cli.js +110 -0
- package/lib/state-cli.js.map +1 -0
- package/lib/state.d.ts +2 -0
- package/lib/state.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/alchemy.ts +9 -0
- package/src/cloudflare/access-application.ts +489 -0
- package/src/cloudflare/access-group.ts +271 -0
- package/src/cloudflare/access-identity-provider.ts +584 -0
- package/src/cloudflare/access-policy.ts +392 -0
- package/src/cloudflare/access-rule.ts +307 -0
- package/src/cloudflare/access-service-token.ts +255 -0
- package/src/cloudflare/index.ts +6 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +20 -3
- package/src/state/file-system-state-store.ts +16 -0
- package/src/state/operations.ts +22 -0
- package/src/state/proxy.ts +4 -0
- package/src/state-cli.ts +134 -0
- package/src/state.ts +2 -0
- package/workers/cloudflare-state-store.js +17 -0
- package/workers/tunnel-proxy.js +1 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Resource, ResourceKind } from "../resource.js";
|
|
2
|
+
import { Secret } from "../secret.js";
|
|
3
|
+
import { logger } from "../util/logger.js";
|
|
4
|
+
import { isCloudflareApiError } from "./api-error.js";
|
|
5
|
+
import { extractCloudflareResult, } from "./api-response.js";
|
|
6
|
+
import { createCloudflareApi, } from "./api.js";
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for {@link AccessIdentityProvider}.
|
|
9
|
+
*/
|
|
10
|
+
export function isAccessIdentityProvider(resource) {
|
|
11
|
+
return resource?.[ResourceKind] === "cloudflare::AccessIdentityProvider";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a Cloudflare Zero Trust [Access identity provider](https://developers.cloudflare.com/cloudflare-one/identity/idp-integration/)
|
|
15
|
+
* which lets users sign in to Access-protected applications.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Built-in One-Time PIN (no IdP setup required).
|
|
19
|
+
* const otp = await AccessIdentityProvider("otp", {
|
|
20
|
+
* type: "onetimepin",
|
|
21
|
+
* name: "Email OTP",
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Google OAuth. clientId is a public OAuth identifier (not a secret).
|
|
26
|
+
* const google = await AccessIdentityProvider("google", {
|
|
27
|
+
* type: "google",
|
|
28
|
+
* name: "Google",
|
|
29
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
30
|
+
* clientSecret: alchemy.secret.env.GOOGLE_CLIENT_SECRET,
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Generic OIDC provider.
|
|
35
|
+
* const oidc = await AccessIdentityProvider("idp", {
|
|
36
|
+
* type: "oidc",
|
|
37
|
+
* name: "Corporate IdP",
|
|
38
|
+
* authUrl: "https://idp.example.com/oauth2/authorize",
|
|
39
|
+
* tokenUrl: "https://idp.example.com/oauth2/token",
|
|
40
|
+
* certsUrl: "https://idp.example.com/oauth2/certs",
|
|
41
|
+
* clientId: "my-app",
|
|
42
|
+
* clientSecret: alchemy.secret.env.IDP_CLIENT_SECRET,
|
|
43
|
+
* scopes: ["openid", "email", "profile"],
|
|
44
|
+
* pkceEnabled: true,
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
export const AccessIdentityProvider = Resource("cloudflare::AccessIdentityProvider", async function (id, props) {
|
|
48
|
+
const api = await createCloudflareApi(props);
|
|
49
|
+
const name = props.name ?? this.scope.createPhysicalName(id);
|
|
50
|
+
const basePath = `/accounts/${api.accountId}/access/identity_providers`;
|
|
51
|
+
if (this.phase === "delete") {
|
|
52
|
+
if (this.output?.id && props.delete !== false) {
|
|
53
|
+
await deleteAccessIdentityProvider(api, this.output.id);
|
|
54
|
+
}
|
|
55
|
+
return this.destroy();
|
|
56
|
+
}
|
|
57
|
+
// type is immutable — recreate if it changed.
|
|
58
|
+
if (this.phase === "update" &&
|
|
59
|
+
this.output &&
|
|
60
|
+
this.output.type !== props.type) {
|
|
61
|
+
this.replace(true);
|
|
62
|
+
}
|
|
63
|
+
// Cloudflare requires `config` to always be present, even for variants
|
|
64
|
+
// that don't take any (e.g. `onetimepin`). Omitting it returns
|
|
65
|
+
// [12130] "unexpected end of JSON input".
|
|
66
|
+
const body = {
|
|
67
|
+
name,
|
|
68
|
+
type: props.type,
|
|
69
|
+
config: extractIdpConfig(props),
|
|
70
|
+
};
|
|
71
|
+
let result;
|
|
72
|
+
if (this.phase === "update" && this.output?.id) {
|
|
73
|
+
result = await extractCloudflareResult(`update access identity provider "${name}"`, api.put(`${basePath}/${this.output.id}`, body));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const adopt = props.adopt ?? this.scope.adopt;
|
|
77
|
+
try {
|
|
78
|
+
result =
|
|
79
|
+
await extractCloudflareResult(`create access identity provider "${name}"`, api.post(basePath, body));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (adopt && isAccessDuplicateNameError(err)) {
|
|
83
|
+
const existing = await findAccessIdentityProviderByName(api, name);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
throw new Error(`Identity provider "${name}" already exists but could not be found for adoption.`, { cause: err });
|
|
86
|
+
}
|
|
87
|
+
logger.log(`Adopting existing access identity provider "${name}" (${existing.id})`);
|
|
88
|
+
result =
|
|
89
|
+
await extractCloudflareResult(`adopt access identity provider "${name}"`, api.put(`${basePath}/${existing.id}`, body));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const rest = { ...props };
|
|
97
|
+
delete rest.adopt;
|
|
98
|
+
delete rest.delete;
|
|
99
|
+
// Output convention (CLAUDE.md): secrets are always wrapped. Strict
|
|
100
|
+
// variants carry `clientSecret` at the top level; the `Other` variant
|
|
101
|
+
// tucks it inside `config`.
|
|
102
|
+
if (typeof rest.clientSecret === "string") {
|
|
103
|
+
rest.clientSecret = Secret.wrap(rest.clientSecret);
|
|
104
|
+
}
|
|
105
|
+
const config = rest.config;
|
|
106
|
+
if (config && typeof config.clientSecret === "string") {
|
|
107
|
+
rest.config = {
|
|
108
|
+
...config,
|
|
109
|
+
clientSecret: Secret.wrap(config.clientSecret),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...rest,
|
|
114
|
+
id: result.id,
|
|
115
|
+
name: result.name,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Top-level prop keys that are *not* part of the IdP-specific configuration
|
|
120
|
+
* (the wire `config` blob) — Alchemy/Cloudflare metadata, the type
|
|
121
|
+
* discriminator, and the explicit `config` escape hatch on the `Other`
|
|
122
|
+
* variant.
|
|
123
|
+
*/
|
|
124
|
+
const IDP_METADATA_KEYS = new Set([
|
|
125
|
+
"name",
|
|
126
|
+
"type",
|
|
127
|
+
"adopt",
|
|
128
|
+
"delete",
|
|
129
|
+
"baseUrl",
|
|
130
|
+
"profile",
|
|
131
|
+
"apiKey",
|
|
132
|
+
"apiToken",
|
|
133
|
+
"accountId",
|
|
134
|
+
"email",
|
|
135
|
+
"config",
|
|
136
|
+
]);
|
|
137
|
+
/**
|
|
138
|
+
* Build the wire-format `config` blob from props. Strict variants store
|
|
139
|
+
* config fields flat at the top level; the `Other` variant uses an explicit
|
|
140
|
+
* nested `config` object as an escape hatch.
|
|
141
|
+
*/
|
|
142
|
+
function extractIdpConfig(props) {
|
|
143
|
+
if ("config" in props && props.config) {
|
|
144
|
+
return camelToSnakeWithSecrets(props.config);
|
|
145
|
+
}
|
|
146
|
+
const flat = {};
|
|
147
|
+
for (const [key, value] of Object.entries(props)) {
|
|
148
|
+
if (!IDP_METADATA_KEYS.has(key) && value !== undefined) {
|
|
149
|
+
flat[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return camelToSnakeWithSecrets(flat);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Convert a camelCase config object to snake_case for the wire, unwrapping
|
|
156
|
+
* any {@link Secret} values along the way. Recurses into arrays of objects
|
|
157
|
+
* (e.g. SAML `headerAttributes`).
|
|
158
|
+
*/
|
|
159
|
+
function camelToSnakeWithSecrets(input) {
|
|
160
|
+
const out = {};
|
|
161
|
+
for (const [key, value] of Object.entries(input)) {
|
|
162
|
+
if (value === undefined)
|
|
163
|
+
continue;
|
|
164
|
+
const snakeKey = key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
165
|
+
out[snakeKey] = transformValue(value);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function transformValue(value) {
|
|
170
|
+
if (value instanceof Secret)
|
|
171
|
+
return value.unencrypted;
|
|
172
|
+
if (Array.isArray(value)) {
|
|
173
|
+
return value.map((item) => item && typeof item === "object" && !Array.isArray(item)
|
|
174
|
+
? camelToSnakeWithSecrets(item)
|
|
175
|
+
: transformValue(item));
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Cloudflare returns 409/400 with an "already exists" message for duplicate
|
|
181
|
+
* IdP names.
|
|
182
|
+
*/
|
|
183
|
+
function isAccessDuplicateNameError(err) {
|
|
184
|
+
if (isCloudflareApiError(err, { status: 409 }) ||
|
|
185
|
+
isCloudflareApiError(err, { status: 400 })) {
|
|
186
|
+
const data = err.errorData;
|
|
187
|
+
return (Array.isArray(data) &&
|
|
188
|
+
data.some((e) => "message" in e && /already exists/i.test(String(e.message))));
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Look up an existing IdP by name across paginated results.
|
|
194
|
+
*/
|
|
195
|
+
async function findAccessIdentityProviderByName(api, name) {
|
|
196
|
+
let page = 1;
|
|
197
|
+
const perPage = 50;
|
|
198
|
+
while (true) {
|
|
199
|
+
const response = await api.get(`/accounts/${api.accountId}/access/identity_providers?page=${page}&per_page=${perPage}`);
|
|
200
|
+
if (!response.ok)
|
|
201
|
+
return null;
|
|
202
|
+
const data = (await response.json());
|
|
203
|
+
const match = data.result.find((p) => p.name === name);
|
|
204
|
+
if (match)
|
|
205
|
+
return match;
|
|
206
|
+
const info = data.result_info;
|
|
207
|
+
if (!info || info.page * info.per_page >= info.total_count)
|
|
208
|
+
return null;
|
|
209
|
+
page++;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Delete an IdP. Cloudflare returns 400 if any Application references it.
|
|
214
|
+
*/
|
|
215
|
+
async function deleteAccessIdentityProvider(api, idpId) {
|
|
216
|
+
const response = await api.delete(`/accounts/${api.accountId}/access/identity_providers/${idpId}`);
|
|
217
|
+
if (!response.ok && response.status !== 404) {
|
|
218
|
+
let body = "";
|
|
219
|
+
try {
|
|
220
|
+
body = await response.text();
|
|
221
|
+
}
|
|
222
|
+
catch { }
|
|
223
|
+
if (/in use|reference|associated/i.test(body)) {
|
|
224
|
+
throw new Error(`Cannot delete identity provider ${idpId}: it is referenced by one or more Access applications. Remove those references first.\n${body}`);
|
|
225
|
+
}
|
|
226
|
+
logger.error(`Error deleting access identity provider ${idpId}: ${response.status} ${response.statusText}\n${body}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=access-identity-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-identity-provider.js","sourceRoot":"","sources":["../../src/cloudflare/access-identity-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { Context } from "../context.ts";
|
|
2
|
+
import { type AccessRule } from "./access-rule.ts";
|
|
3
|
+
import { type CloudflareApiOptions } from "./api.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Access decision a policy applies when its rules match.
|
|
6
|
+
* - `allow`: grant access when an authenticated user matches `include`.
|
|
7
|
+
* - `deny`: deny access when matched.
|
|
8
|
+
* - `non_identity`: allow without identity (e.g. service tokens, IP allowlists).
|
|
9
|
+
* - `bypass`: skip Access entirely (no auth required).
|
|
10
|
+
*/
|
|
11
|
+
export type AccessPolicyDecision = "allow" | "deny" | "non_identity" | "bypass";
|
|
12
|
+
/**
|
|
13
|
+
* An approval group authorising an access request when
|
|
14
|
+
* `approvalRequired: true`.
|
|
15
|
+
*/
|
|
16
|
+
export interface AccessPolicyApprovalGroup {
|
|
17
|
+
/** Number of approvals required from this group. */
|
|
18
|
+
approvalsNeeded: number;
|
|
19
|
+
/** Email addresses of approvers. */
|
|
20
|
+
emailAddresses?: string[];
|
|
21
|
+
/** UUID of an Access email list. */
|
|
22
|
+
emailListUuid?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Optional MFA enforcement on top of the IdP's authentication.
|
|
26
|
+
*/
|
|
27
|
+
export interface AccessPolicyMfaConfig {
|
|
28
|
+
allowedAuthenticators?: string[];
|
|
29
|
+
mfaDisabled?: boolean;
|
|
30
|
+
/** Cloudflare duration string, e.g. `"30m"`. */
|
|
31
|
+
sessionDuration?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Connection-protocol-specific options (currently only RDP clipboard
|
|
35
|
+
* formats are supported by Cloudflare).
|
|
36
|
+
*/
|
|
37
|
+
export interface AccessPolicyConnectionRules {
|
|
38
|
+
rdp?: {
|
|
39
|
+
allowedClipboardFormats?: ("text" | "image" | "files")[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Properties for creating or updating an {@link AccessPolicy}.
|
|
44
|
+
*/
|
|
45
|
+
export interface AccessPolicyProps extends CloudflareApiOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Display name of the policy.
|
|
48
|
+
*
|
|
49
|
+
* @default ${app}-${stage}-${id}
|
|
50
|
+
*/
|
|
51
|
+
name?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Decision the policy applies. **Immutable** — changing the decision will
|
|
54
|
+
* trigger replacement of the underlying Cloudflare resource.
|
|
55
|
+
*/
|
|
56
|
+
decision: AccessPolicyDecision;
|
|
57
|
+
/**
|
|
58
|
+
* Rules a request must match to be considered (OR logic). Must be
|
|
59
|
+
* non-empty — Cloudflare rejects policies with no include rules.
|
|
60
|
+
*/
|
|
61
|
+
include: AccessRule[];
|
|
62
|
+
/**
|
|
63
|
+
* Rules that, when matched, exclude the request from this policy.
|
|
64
|
+
*/
|
|
65
|
+
exclude?: AccessRule[];
|
|
66
|
+
/**
|
|
67
|
+
* Rules that must additionally match (AND logic).
|
|
68
|
+
*/
|
|
69
|
+
require?: AccessRule[];
|
|
70
|
+
/**
|
|
71
|
+
* Require explicit approval before granting access.
|
|
72
|
+
*/
|
|
73
|
+
approvalRequired?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Approver groups consulted when `approvalRequired: true`.
|
|
76
|
+
*/
|
|
77
|
+
approvalGroups?: AccessPolicyApprovalGroup[];
|
|
78
|
+
/**
|
|
79
|
+
* Prompt the user for a purpose justification on each access.
|
|
80
|
+
*/
|
|
81
|
+
purposeJustificationRequired?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Prompt text shown when `purposeJustificationRequired` is true.
|
|
84
|
+
*/
|
|
85
|
+
purposeJustificationPrompt?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Force isolated browser rendering for this policy.
|
|
88
|
+
*/
|
|
89
|
+
isolationRequired?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Optional MFA enforcement.
|
|
92
|
+
*/
|
|
93
|
+
mfaConfig?: AccessPolicyMfaConfig;
|
|
94
|
+
/**
|
|
95
|
+
* Override the default Access session duration (Cloudflare duration string).
|
|
96
|
+
*/
|
|
97
|
+
sessionDuration?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Per-protocol connection rules (e.g. RDP clipboard restrictions).
|
|
100
|
+
*/
|
|
101
|
+
connectionRules?: AccessPolicyConnectionRules;
|
|
102
|
+
/**
|
|
103
|
+
* Adopt an existing policy with the same name instead of failing.
|
|
104
|
+
*
|
|
105
|
+
* @default false
|
|
106
|
+
*/
|
|
107
|
+
adopt?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Whether to delete the policy when removed from Alchemy.
|
|
110
|
+
*
|
|
111
|
+
* @default true
|
|
112
|
+
*/
|
|
113
|
+
delete?: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Output for an {@link AccessPolicy}.
|
|
117
|
+
*/
|
|
118
|
+
export type AccessPolicy = Omit<AccessPolicyProps, "adopt" | "delete"> & {
|
|
119
|
+
/** Cloudflare-assigned policy UUID. */
|
|
120
|
+
id: string;
|
|
121
|
+
/** Display name. */
|
|
122
|
+
name: string;
|
|
123
|
+
/** Number of applications currently referencing this policy. */
|
|
124
|
+
appCount: number;
|
|
125
|
+
/** ISO 8601 creation timestamp. */
|
|
126
|
+
createdAt: string;
|
|
127
|
+
/** ISO 8601 last-update timestamp. */
|
|
128
|
+
updatedAt: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Type guard for {@link AccessPolicy}.
|
|
132
|
+
*/
|
|
133
|
+
export declare function isAccessPolicy(resource: any): resource is AccessPolicy;
|
|
134
|
+
/**
|
|
135
|
+
* Creates a reusable Cloudflare Zero Trust [Access policy](https://developers.cloudflare.com/cloudflare-one/policies/access/)
|
|
136
|
+
* that can be attached to one or more {@link AccessApplication} resources.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Allow employees from a specific email domain.
|
|
140
|
+
* const employees = await AccessPolicy("employees", {
|
|
141
|
+
* name: "Employees",
|
|
142
|
+
* decision: "allow",
|
|
143
|
+
* include: [{ email_domain: { domain: "acme.com" } }],
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // Bypass Access for an office IP range.
|
|
148
|
+
* const officeBypass = await AccessPolicy("office-bypass", {
|
|
149
|
+
* name: "Office Bypass",
|
|
150
|
+
* decision: "bypass",
|
|
151
|
+
* include: [{ ip: { ip: "203.0.113.0/24" } }],
|
|
152
|
+
* });
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* // Reference an AccessGroup and require approval.
|
|
156
|
+
* const sensitive = await AccessPolicy("sensitive", {
|
|
157
|
+
* name: "Sensitive admin access",
|
|
158
|
+
* decision: "allow",
|
|
159
|
+
* include: [{ group: { id: adminGroup } }],
|
|
160
|
+
* approvalRequired: true,
|
|
161
|
+
* approvalGroups: [
|
|
162
|
+
* { approvalsNeeded: 2, emailAddresses: ["security@acme.com"] },
|
|
163
|
+
* ],
|
|
164
|
+
* isolationRequired: true,
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
export declare const AccessPolicy: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<AccessPolicy>, id: string, props: AccessPolicyProps) => Promise<AccessPolicy>);
|
|
168
|
+
//# sourceMappingURL=access-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-policy.d.ts","sourceRoot":"","sources":["../../src/cloudflare/access-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMxE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEhF;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,GAAG,CAAC,EAAE;QACJ,uBAAuB,CAAC,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC;KAC1D,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,oBAAoB;IAC7D;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,EAAE,oBAAoB,CAAC;IAE/B;;;OAGG;IACH,OAAO,EAAE,UAAU,EAAE,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IAEvB;;OAEG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,cAAc,CAAC,EAAE,yBAAyB,EAAE,CAAC;IAE7C;;OAEG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;IAEvC;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IAEpC;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;OAEG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAElC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAE9C;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG;IACvE,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,GAAG,GAAG,QAAQ,IAAI,YAAY,CAEtE;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,YAAY,yFAGf,OAAO,CAAC,YAAY,CAAC,MACvB,MAAM,SACH,iBAAiB,KACvB,OAAO,CAAC,YAAY,CAAC,CAwHzB,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Resource, ResourceKind } from "../resource.js";
|
|
2
|
+
import { logger } from "../util/logger.js";
|
|
3
|
+
import { serializeAccessRule } from "./access-rule.js";
|
|
4
|
+
import { isCloudflareApiError } from "./api-error.js";
|
|
5
|
+
import { extractCloudflareResult, } from "./api-response.js";
|
|
6
|
+
import { createCloudflareApi, } from "./api.js";
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for {@link AccessPolicy}.
|
|
9
|
+
*/
|
|
10
|
+
export function isAccessPolicy(resource) {
|
|
11
|
+
return resource?.[ResourceKind] === "cloudflare::AccessPolicy";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a reusable Cloudflare Zero Trust [Access policy](https://developers.cloudflare.com/cloudflare-one/policies/access/)
|
|
15
|
+
* that can be attached to one or more {@link AccessApplication} resources.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Allow employees from a specific email domain.
|
|
19
|
+
* const employees = await AccessPolicy("employees", {
|
|
20
|
+
* name: "Employees",
|
|
21
|
+
* decision: "allow",
|
|
22
|
+
* include: [{ email_domain: { domain: "acme.com" } }],
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Bypass Access for an office IP range.
|
|
27
|
+
* const officeBypass = await AccessPolicy("office-bypass", {
|
|
28
|
+
* name: "Office Bypass",
|
|
29
|
+
* decision: "bypass",
|
|
30
|
+
* include: [{ ip: { ip: "203.0.113.0/24" } }],
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Reference an AccessGroup and require approval.
|
|
35
|
+
* const sensitive = await AccessPolicy("sensitive", {
|
|
36
|
+
* name: "Sensitive admin access",
|
|
37
|
+
* decision: "allow",
|
|
38
|
+
* include: [{ group: { id: adminGroup } }],
|
|
39
|
+
* approvalRequired: true,
|
|
40
|
+
* approvalGroups: [
|
|
41
|
+
* { approvalsNeeded: 2, emailAddresses: ["security@acme.com"] },
|
|
42
|
+
* ],
|
|
43
|
+
* isolationRequired: true,
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export const AccessPolicy = Resource("cloudflare::AccessPolicy", async function (id, props) {
|
|
47
|
+
const api = await createCloudflareApi(props);
|
|
48
|
+
const name = props.name ?? this.scope.createPhysicalName(id);
|
|
49
|
+
const basePath = `/accounts/${api.accountId}/access/policies`;
|
|
50
|
+
if (this.phase === "delete") {
|
|
51
|
+
if (this.output?.id && props.delete !== false) {
|
|
52
|
+
await deleteAccessPolicy(api, this.output.id);
|
|
53
|
+
}
|
|
54
|
+
return this.destroy();
|
|
55
|
+
}
|
|
56
|
+
if (!props.include || props.include.length === 0) {
|
|
57
|
+
throw new Error(`AccessPolicy "${name}" requires at least one rule in 'include'.`);
|
|
58
|
+
}
|
|
59
|
+
// decision is immutable — recreate if it changed.
|
|
60
|
+
if (this.phase === "update" &&
|
|
61
|
+
this.output &&
|
|
62
|
+
this.output.decision !== props.decision) {
|
|
63
|
+
this.replace(true);
|
|
64
|
+
}
|
|
65
|
+
const body = {
|
|
66
|
+
name,
|
|
67
|
+
decision: props.decision,
|
|
68
|
+
reusable: true,
|
|
69
|
+
include: props.include.map(serializeAccessRule),
|
|
70
|
+
exclude: (props.exclude ?? []).map(serializeAccessRule),
|
|
71
|
+
require: (props.require ?? []).map(serializeAccessRule),
|
|
72
|
+
};
|
|
73
|
+
if (props.approvalRequired !== undefined)
|
|
74
|
+
body.approval_required = props.approvalRequired;
|
|
75
|
+
if (props.approvalGroups)
|
|
76
|
+
body.approval_groups = props.approvalGroups.map((g) => ({
|
|
77
|
+
approvals_needed: g.approvalsNeeded,
|
|
78
|
+
email_addresses: g.emailAddresses,
|
|
79
|
+
email_list_uuid: g.emailListUuid,
|
|
80
|
+
}));
|
|
81
|
+
if (props.purposeJustificationRequired !== undefined)
|
|
82
|
+
body.purpose_justification_required = props.purposeJustificationRequired;
|
|
83
|
+
if (props.purposeJustificationPrompt !== undefined)
|
|
84
|
+
body.purpose_justification_prompt = props.purposeJustificationPrompt;
|
|
85
|
+
if (props.isolationRequired !== undefined)
|
|
86
|
+
body.isolation_required = props.isolationRequired;
|
|
87
|
+
if (props.sessionDuration !== undefined)
|
|
88
|
+
body.session_duration = props.sessionDuration;
|
|
89
|
+
if (props.mfaConfig)
|
|
90
|
+
body.mfa_config = {
|
|
91
|
+
allowed_authenticators: props.mfaConfig.allowedAuthenticators,
|
|
92
|
+
mfa_disabled: props.mfaConfig.mfaDisabled,
|
|
93
|
+
session_duration: props.mfaConfig.sessionDuration,
|
|
94
|
+
};
|
|
95
|
+
if (props.connectionRules?.rdp)
|
|
96
|
+
body.connection_rules = {
|
|
97
|
+
rdp: {
|
|
98
|
+
allowed_clipboard_formats: props.connectionRules.rdp.allowedClipboardFormats,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
let policy;
|
|
102
|
+
if (this.phase === "update" && this.output?.id) {
|
|
103
|
+
policy = await extractCloudflareResult(`update access policy "${name}"`, api.put(`${basePath}/${this.output.id}`, body));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const adopt = props.adopt ?? this.scope.adopt;
|
|
107
|
+
try {
|
|
108
|
+
policy = await extractCloudflareResult(`create access policy "${name}"`, api.post(basePath, body));
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (adopt && isAccessDuplicateNameError(err)) {
|
|
112
|
+
const existing = await findAccessPolicyByName(api, name);
|
|
113
|
+
if (!existing) {
|
|
114
|
+
throw new Error(`Access policy "${name}" already exists but could not be found for adoption.`, { cause: err });
|
|
115
|
+
}
|
|
116
|
+
logger.log(`Adopting existing access policy "${name}" (${existing.id})`);
|
|
117
|
+
policy = await extractCloudflareResult(`adopt access policy "${name}"`, api.put(`${basePath}/${existing.id}`, body));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
id: policy.id,
|
|
126
|
+
name: policy.name,
|
|
127
|
+
decision: policy.decision,
|
|
128
|
+
include: props.include,
|
|
129
|
+
exclude: props.exclude,
|
|
130
|
+
require: props.require,
|
|
131
|
+
approvalRequired: props.approvalRequired,
|
|
132
|
+
approvalGroups: props.approvalGroups,
|
|
133
|
+
purposeJustificationRequired: props.purposeJustificationRequired,
|
|
134
|
+
purposeJustificationPrompt: props.purposeJustificationPrompt,
|
|
135
|
+
isolationRequired: props.isolationRequired,
|
|
136
|
+
mfaConfig: props.mfaConfig,
|
|
137
|
+
sessionDuration: props.sessionDuration,
|
|
138
|
+
connectionRules: props.connectionRules,
|
|
139
|
+
appCount: policy.app_count ?? 0,
|
|
140
|
+
createdAt: policy.created_at,
|
|
141
|
+
updatedAt: policy.updated_at,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
function isAccessDuplicateNameError(err) {
|
|
145
|
+
if (isCloudflareApiError(err, { status: 409 }) ||
|
|
146
|
+
isCloudflareApiError(err, { status: 400 })) {
|
|
147
|
+
const data = err.errorData;
|
|
148
|
+
return (Array.isArray(data) &&
|
|
149
|
+
data.some((e) => "message" in e && /already exists/i.test(String(e.message))));
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
async function findAccessPolicyByName(api, name) {
|
|
154
|
+
let page = 1;
|
|
155
|
+
const perPage = 50;
|
|
156
|
+
while (true) {
|
|
157
|
+
const response = await api.get(`/accounts/${api.accountId}/access/policies?page=${page}&per_page=${perPage}`);
|
|
158
|
+
if (!response.ok)
|
|
159
|
+
return null;
|
|
160
|
+
const data = (await response.json());
|
|
161
|
+
const match = data.result.find((p) => p.name === name);
|
|
162
|
+
if (match)
|
|
163
|
+
return match;
|
|
164
|
+
const info = data.result_info;
|
|
165
|
+
if (!info || info.page * info.per_page >= info.total_count)
|
|
166
|
+
return null;
|
|
167
|
+
page++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function deleteAccessPolicy(api, policyId) {
|
|
171
|
+
const response = await api.delete(`/accounts/${api.accountId}/access/policies/${policyId}`);
|
|
172
|
+
if (!response.ok && response.status !== 404) {
|
|
173
|
+
logger.error(`Error deleting access policy ${policyId}: ${response.status} ${response.statusText}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=access-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-policy.js","sourceRoot":"","sources":["../../src/cloudflare/access-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAmB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EACL,uBAAuB,GAExB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,mBAAmB,GAGpB,MAAM,UAAU,CAAC;AAoJlB;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAa;IAC1C,OAAO,QAAQ,EAAE,CAAC,YAAY,CAAC,KAAK,0BAA0B,CAAC;AACjE,CAAC;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAClC,0BAA0B,EAC1B,KAAK,WAEH,EAAU,EACV,KAAwB;IAExB,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,aAAa,GAAG,CAAC,SAAS,kBAAkB,CAAC;IAE9D,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC9C,MAAM,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,4CAA4C,CAClE,CAAC;IACJ,CAAC;IAED,kDAAkD;IAClD,IACE,IAAI,CAAC,KAAK,KAAK,QAAQ;QACvB,IAAI,CAAC,MAAM;QACX,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,EACvC,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,IAAI,GAA4B;QACpC,IAAI;QACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC/C,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACvD,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;KACxD,CAAC;IACF,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS;QACtC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAClD,IAAI,KAAK,CAAC,cAAc;QACtB,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,gBAAgB,EAAE,CAAC,CAAC,eAAe;YACnC,eAAe,EAAE,CAAC,CAAC,cAAc;YACjC,eAAe,EAAE,CAAC,CAAC,aAAa;SACjC,CAAC,CAAC,CAAC;IACN,IAAI,KAAK,CAAC,4BAA4B,KAAK,SAAS;QAClD,IAAI,CAAC,8BAA8B,GAAG,KAAK,CAAC,4BAA4B,CAAC;IAC3E,IAAI,KAAK,CAAC,0BAA0B,KAAK,SAAS;QAChD,IAAI,CAAC,4BAA4B,GAAG,KAAK,CAAC,0BAA0B,CAAC;IACvE,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS;QACvC,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC,iBAAiB,CAAC;IACpD,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;QACrC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,eAAe,CAAC;IAChD,IAAI,KAAK,CAAC,SAAS;QACjB,IAAI,CAAC,UAAU,GAAG;YAChB,sBAAsB,EAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB;YAC7D,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW;YACzC,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,eAAe;SAClD,CAAC;IACJ,IAAI,KAAK,CAAC,eAAe,EAAE,GAAG;QAC5B,IAAI,CAAC,gBAAgB,GAAG;YACtB,GAAG,EAAE;gBACH,yBAAyB,EACvB,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,uBAAuB;aACpD;SACF,CAAC;IAEJ,IAAI,MAA8B,CAAC;IACnC,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC;QAC/C,MAAM,GAAG,MAAM,uBAAuB,CACpC,yBAAyB,IAAI,GAAG,EAChC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAC/C,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,uBAAuB,CACpC,yBAAyB,IAAI,GAAG,EAChC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CACzB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,KAAK,IAAI,0BAA0B,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,uDAAuD,EAC7E,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,GAAG,CACR,oCAAoC,IAAI,MAAM,QAAQ,CAAC,EAAE,GAAG,CAC7D,CAAC;gBACF,MAAM,GAAG,MAAM,uBAAuB,CACpC,wBAAwB,IAAI,GAAG,EAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAC5C,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,4BAA4B,EAAE,KAAK,CAAC,4BAA4B;QAChE,0BAA0B,EAAE,KAAK,CAAC,0BAA0B;QAC5D,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,QAAQ,EAAE,MAAM,CAAC,SAAS,IAAI,CAAC;QAC/B,SAAS,EAAE,MAAM,CAAC,UAAU;QAC5B,SAAS,EAAE,MAAM,CAAC,UAAU;KAC7B,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,SAAS,0BAA0B,CAAC,GAAY;IAC9C,IACE,oBAAoB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QAC1C,oBAAoB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAC1C,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC;QAC3B,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACnB,IAAI,CAAC,IAAI,CACP,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,IAAI,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CACnE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,sBAAsB,CACnC,GAAkB,EAClB,IAAY;IAEZ,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,OAAO,GAAG,EAAE,CAAC;IACnB,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,CAC5B,aAAa,GAAG,CAAC,SAAS,yBAAyB,IAAI,aAAa,OAAO,EAAE,CAC9E,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,IAAI,GACR,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsD,CAAC;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACvD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;QAC9B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACxE,IAAI,EAAE,CAAC;IACT,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,GAAkB,EAClB,QAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAC/B,aAAa,GAAG,CAAC,SAAS,oBAAoB,QAAQ,EAAE,CACzD,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5C,MAAM,CAAC,KAAK,CACV,gCAAgC,QAAQ,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CACtF,CAAC;IACJ,CAAC;AACH,CAAC"}
|