← back to Open Seo

alchemy.preview-access.run.ts

50 lines

import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import {
  emailAccessGate,
  previewWildcard,
  readWorkersSubdomain,
  requireAllowedEmails,
} from "./alchemy.access.ts";

// Persistent account-level security boundary for every ephemeral preview.
// This stack is intentionally separate from alchemy.run.ts: destroying one
// preview must never remove the wildcard Access application protecting all of
// the others. The wildcard hostname derives from the same worker naming the
// deploy stack uses (alchemy.access.ts).
//
// This gates the stable stage hostname (`open-seo-<stage>.<sub>`). Cloudflare
// version preview URLs (`<version>-open-seo-<stage>.<sub>`) sit outside this
// wildcard, but alchemy uploads each version with no preview provisioned
// (`has_preview: false`), so none are served — see docs/PREVIEW_DEPLOYMENTS.md.
export default Alchemy.Stack(
  "open-seo-preview-access",
  {
    providers: Cloudflare.providers(),
    state: Cloudflare.state(),
  },
  Effect.gen(function* () {
    const subdomain = yield* readWorkersSubdomain({ required: true });
    const allowedEmails = yield* requireAllowedEmails(
      "Set ACCESS_ALLOWED_EMAILS to the comma-separated preview testers.",
    );

    const hostname = previewWildcard(subdomain);
    const application = yield* emailAccessGate({
      policyId: "PreviewAllowTeam",
      applicationId: "PreviewAccess",
      policyName: "open-seo preview team",
      applicationName: "open-seo preview environments",
      domain: hostname,
      emails: allowedEmails,
    });

    return {
      hostname,
      applicationId: application.applicationId,
      aud: application.aud,
    };
  }),
);