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 { Staging, Zone } from "./shared.ts";
9
10/**
11 * Effect-native Worker that mints a certificate at runtime: the
12 * {@link ACME.IssueCertificate} binding signs as the staging account bound
13 * into the Worker, DNS-01 goes through {@link Cloudflare.DNS.WriteDns}.
14 */
15export default class AcmeIssueWorker extends Cloudflare.Worker<AcmeIssueWorker>()(
16 "AcmeIssueWorker",
17 {
18 main: import.meta.url,
19 },
20 Effect.gen(function* () {
21 const acme = yield* ACME.IssueCertificate(Staging);
22 const dns = yield* Cloudflare.DNS.WriteDns(Zone);
23 const solver = Cloudflare.DNS.acmeDnsSolver(dns);
24
25 return {
26 fetch: Effect.gen(function* () {
27 const request = yield* HttpServerRequest;
28 const url = new URL(request.url, "http://worker");
29 if (url.pathname !== "/issue") {
30 return yield* HttpServerResponse.json({ ok: true, version: 2 });
31 }
32 const name = url.searchParams.get("name") ?? "";
33 return yield* Effect.gen(function* () {
34 const issued = yield* acme.issue({ identifiers: [name], solver });
35 const parsed = yield* ACME.parseCertificate(issued.certificate);
36 yield* acme.revoke({
37 certificate: issued.certificate,
38 privateKey: issued.privateKey,
39 });
40 return yield* HttpServerResponse.json({
41 issuer: issued.issuer,
42 notAfter: issued.notAfter,
43 serial: issued.serial,
44 dnsNames: parsed.dnsNames,
45 hasKey: Redacted.value(issued.privateKey).includes(
46 "BEGIN PRIVATE KEY",
47 ),
48 chainLength: ACME.splitPemChain(issued.chain).length,
49 });
50 }).pipe(
51 // Failures and defects alike come back as JSON so the test can
52 // show what went wrong inside the Worker.
53 Effect.catchCause((cause) =>
54 HttpServerResponse.json(
55 { error: Cause.pretty(cause) },
56 { status: 500 },
57 ),
58 ),
59 );
60 }),
61 };
62 }).pipe(
63 Effect.provide(ACME.IssueCertificateHttp),
64 Effect.provide(Cloudflare.DNS.WriteDnsHttp),
65 ),
66) {}