1import * as ACME from "@/ACME";
2import * as Cloudflare from "@/Cloudflare";
3import * as Cause from "effect/Cause";
4import * as Effect from "effect/Effect";
5import * as Redacted from "effect/Redacted";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import { ZeroSSLAccount, Zone } from "./shared.ts";
9
10/**
11 * The deployed-Worker variant of `issue-worker.ts`: same routes, but the
12 * bound account is ZeroSSL — Let's Encrypt answers 525 to Workers egress.
13 */
14export default class AcmeIssueZeroSslWorker extends Cloudflare.Worker<AcmeIssueZeroSslWorker>()(
15 "AcmeIssueZeroSslWorker",
16 {
17 main: import.meta.url,
18 },
19 Effect.gen(function* () {
20 const acme = yield* ACME.IssueCertificate(ZeroSSLAccount);
21 const dns = yield* Cloudflare.DNS.WriteDns(Zone);
22 const solver = Cloudflare.DNS.acmeDnsSolver(dns);
23
24 return {
25 fetch: Effect.gen(function* () {
26 const request = yield* HttpServerRequest;
27 const url = new URL(request.url, "http://worker");
28 if (url.pathname !== "/issue") {
29 return yield* HttpServerResponse.json({ ok: true, version: 2 });
30 }
31 const name = url.searchParams.get("name") ?? "";
32 return yield* Effect.gen(function* () {
33 const issued = yield* acme.issue({ identifiers: [name], solver });
34 const parsed = yield* ACME.parseCertificate(issued.certificate);
35 yield* acme.revoke({
36 certificate: issued.certificate,
37 privateKey: issued.privateKey,
38 });
39 return yield* HttpServerResponse.json({
40 issuer: issued.issuer,
41 notAfter: issued.notAfter,
42 serial: issued.serial,
43 dnsNames: parsed.dnsNames,
44 hasKey: Redacted.value(issued.privateKey).includes(
45 "BEGIN PRIVATE KEY",
46 ),
47 chainLength: ACME.splitPemChain(issued.chain).length,
48 });
49 }).pipe(
50 // Failures and defects alike come back as JSON so the test can
51 // show what went wrong inside the Worker.
52 Effect.catchCause((cause) =>
53 HttpServerResponse.json(
54 { error: Cause.pretty(cause) },
55 { status: 500 },
56 ),
57 ),
58 );
59 }),
60 };
61 }).pipe(
62 Effect.provide(ACME.IssueCertificateHttp),
63 Effect.provide(Cloudflare.DNS.WriteDnsHttp),
64 ),
65) {}