1import * as originTls from "@distilled.cloud/cloudflare/origin-tls-client-auth";
2import * as Effect from "effect/Effect";
3import * as Predicate from "effect/Predicate";
4
5import * as Provider from "../../Provider.ts";
6import { Resource } from "../../Resource.ts";
7import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
8import type { Providers } from "../Providers.ts";
9import { listAllZones } from "../Zone/lookup.ts";
10
11const TypeId = "Cloudflare.OriginTlsClientAuth.Setting" as const;
12type TypeId = typeof TypeId;
13
14export type SettingProps = {
15 /**
16 * Zone the setting belongs to. Stable — changing the zone triggers a
17 * replacement (the old zone's setting is restored to the value it had
18 * before Alchemy managed it).
19 */
20 zoneId: string;
21 /**
22 * Whether zone-level Authenticated Origin Pulls is enabled. When enabled,
23 * Cloudflare presents the zone's uploaded client certificate
24 * ({@link Certificate}) to your origin on every pull.
25 *
26 * Mutable — updated in place.
27 * @default false (Cloudflare's default)
28 */
29 enabled: boolean;
30};
31
32export type SettingAttributes = {
33 /** Zone the setting belongs to. */
34 zoneId: string;
35 /** Whether zone-level Authenticated Origin Pulls is currently enabled. */
36 enabled: boolean;
37 /**
38 * The value the setting had before Alchemy first managed it. Restored on
39 * destroy, so deleting the resource puts the zone back the way it was
40 * found.
41 */
42 initialEnabled: boolean;
43};
44
45export type Setting = Resource<
46 TypeId,
47 SettingProps,
48 SettingAttributes,
49 never,
50 Providers
51>;
52
53/**
54 * The zone-level Authenticated Origin Pulls (AOP) toggle
55 * (`/zones/{zone_id}/origin_tls_client_auth/settings`).
56 *
57 * The setting is a singleton — it always exists on every zone (Cloudflare
58 * default `false`), so this resource never creates or deletes anything
59 * physical. Reconcile flips the flag when the observed value differs from
60 * the desired one; destroy restores the value the setting had before
61 * Alchemy first managed it (captured as `initialEnabled`).
62 *
63 * Enabling AOP only has effect once a zone client certificate is uploaded
64 * ({@link Certificate}) and your origin is configured to
65 * verify it — enabling the flag alone does not break traffic unless the
66 * origin enforces mTLS.
67 * ### Enabling Authenticated Origin Pulls
68 * **Example:** Enable zone-level AOP
69 * ```typescript
70 * const cert = yield* Cloudflare.OriginTlsClientAuth.Certificate("AopCert", {
71 * zoneId: zone.zoneId,
72 * certificate: clientCertPem,
73 * privateKey: yield* Config.Redacted("AOP_CLIENT_KEY"),
74 * });
75 *
76 * yield* Cloudflare.OriginTlsClientAuth.Setting("Aop", {
77 * zoneId: zone.zoneId,
78 * enabled: true,
79 * });
80 * ```
81 *
82 * **Example:** Pin AOP off
83 * ```typescript
84 * yield* Cloudflare.OriginTlsClientAuth.Setting("Aop", {
85 * zoneId: zone.zoneId,
86 * enabled: false,
87 * });
88 * ```
89 *
90 * @see https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/
91 *
92 * @resource
93 * @product Origin TLS Client Auth
94 * @category SSL/TLS & Certificates
95 */
96export const Setting = Resource<Setting>(TypeId);
97
98/**
99 * Returns true if the given value is an Setting resource.
100 */
101export const isSetting = (value: unknown): value is Setting =>
102 Predicate.hasProperty(value, "Type") && value.Type === TypeId;
103
104export const SettingProvider = () =>
105 Provider.succeed(Setting, {
106 nuke: { singleton: true },
107 stables: ["zoneId", "initialEnabled"],
108
109 list: Effect.fn(function* () {
110 const { accountId } = yield* yield* CloudflareEnvironment;
111 // No account-wide API for this zone singleton — enumerate every
112 // zone in the account and read its setting (every zone has one).
113 const allZones = yield* listAllZones(accountId);
114 const rows = yield* Effect.forEach(
115 allZones.map((zone) => zone.id),
116 (zoneId) =>
117 originTls.getSetting({ zoneId }).pipe(
118 Effect.map((observed): SettingAttributes => {
119 const enabled = observed.enabled ?? false;
120 // Enumeration adopts the live value as the pre-management
121 // baseline, mirroring a cold `read`.
122 return { zoneId, enabled, initialEnabled: enabled };
123 }),
124 // Plan-gated or partial zones reject the route; skip them.
125 Effect.catchTag("InvalidRoute", () => Effect.succeed(undefined)),
126 ),
127 { concurrency: 10 },
128 );
129 return rows.filter((row): row is SettingAttributes => row !== undefined);
130 }),
131
132 diff: Effect.fn(function* ({ olds = {}, news, output }) {
133 const o = olds as SettingProps;
134 const n = news as SettingProps;
135 // zoneId is Input<string>; compare only once both sides are concrete.
136 const oldZoneId =
137 output?.zoneId ?? (typeof o.zoneId === "string" ? o.zoneId : undefined);
138 if (
139 oldZoneId !== undefined &&
140 typeof n.zoneId === "string" &&
141 oldZoneId !== n.zoneId
142 ) {
143 return { action: "replace" } as const;
144 }
145 return undefined;
146 }),
147
148 read: Effect.fn(function* ({ output, olds }) {
149 const zoneId = output?.zoneId ?? (olds?.zoneId as string | undefined);
150 if (!zoneId) return undefined;
151 const observed = yield* originTls.getSetting({ zoneId });
152 const enabled = observed.enabled ?? false;
153 // The setting is a singleton that always exists with a Cloudflare
154 // default — there is nothing to "own", so a cold read adopts freely
155 // (never `Unowned`). The observed value at adoption time becomes the
156 // `initialEnabled` restored on destroy.
157 const initialEnabled =
158 output !== undefined ? output.initialEnabled : enabled;
159 return { zoneId, enabled, initialEnabled };
160 }),
161
162 reconcile: Effect.fn(function* ({ news, output }) {
163 // Inputs have been resolved to concrete strings by Plan.
164 const zoneId = news.zoneId as string;
165
166 // 1. Observe — the setting always exists; read its live value.
167 const observed = yield* originTls.getSetting({ zoneId });
168 const observedEnabled = observed.enabled ?? false;
169
170 // 2. Capture — the pre-management value, restored on destroy.
171 // `output` (including an adoption read) already carries it;
172 // otherwise this is our first touch and the observed value is the
173 // zone's original.
174 const initialEnabled =
175 output !== undefined ? output.initialEnabled : observedEnabled;
176
177 // 3. Sync — put only when the observed value differs.
178 if (observedEnabled === news.enabled) {
179 return { zoneId, enabled: observedEnabled, initialEnabled };
180 }
181 const updated = yield* originTls.putSetting({
182 zoneId,
183 enabled: news.enabled,
184 });
185 return {
186 zoneId,
187 enabled: updated.enabled ?? news.enabled,
188 initialEnabled,
189 };
190 }),
191
192 delete: Effect.fn(function* ({ output }) {
193 const { zoneId, initialEnabled } = output;
194 // Observe — restore the pre-management value; skip the call when it
195 // already matches (idempotent re-delete after a crashed run).
196 const observed = yield* originTls.getSetting({ zoneId });
197 if ((observed.enabled ?? false) === initialEnabled) return;
198 yield* originTls.putSetting({ zoneId, enabled: initialEnabled });
199 }),
200 });