-
Notifications
You must be signed in to change notification settings - Fork 710
Expand file tree
/
Copy pathalchemy.access.ts
More file actions
81 lines (73 loc) · 3.05 KB
/
Copy pathalchemy.access.ts
File metadata and controls
81 lines (73 loc) · 3.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// The contract shared by the two email-gated Cloudflare Access boundaries —
// the persistent preview wildcard (alchemy.preview-access.run.ts) and the
// per-stage self-host gate (alchemy.run.ts). Worker naming, the
// WORKERS_SUBDOMAIN shape, the allowed-emails parsing, and the
// policy/application shape define who gets through which hostnames; keep them
// in one place so the two gates cannot drift. The one copy that can't import
// this module is the shell in .github/workflows/pr-preview.yml — its
// `open-seo-<stage>` naming stays comment-synced (and is backstopped by the
// workflow's Access verify step).
import * as Cloudflare from "alchemy/Cloudflare";
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
const WORKER_PREFIX = "open-seo";
// The one stage that adopts openseo.so's live hosted resources (unsuffixed
// names, app.openseo.so domain, Postgres). Deliberately not "prod" so a
// self-hoster's stage name can't collide with the adoption path.
export const HOSTED_PROD_STAGE = "hosted-prod";
export const workerName = (stage: string) =>
stage === HOSTED_PROD_STAGE ? WORKER_PREFIX : `${WORKER_PREFIX}-${stage}`;
// Matches every preview worker hostname; production's unsuffixed worker does
// not match (Access allows one wildcard per dot-label).
export const previewWildcard = (subdomain: string) =>
`${WORKER_PREFIX}-*.${subdomain}`;
export const readWorkersSubdomain = ({ required }: { required: boolean }) =>
Effect.gen(function* () {
const subdomain = (yield* Config.string("WORKERS_SUBDOMAIN").pipe(
Config.withDefault(""),
)).trim();
if (subdomain.endsWith(".workers.dev") || (!subdomain && !required)) {
return subdomain;
}
return yield* Effect.die(
new Error(
`Set WORKERS_SUBDOMAIN to the account's full workers.dev subdomain (shown under Workers & Pages)${required ? "." : ", or leave it unset."}`,
),
);
});
/** Reads ACCESS_ALLOWED_EMAILS; dies with `remedy` when none are set. */
export const requireAllowedEmails = (remedy: string) =>
Effect.gen(function* () {
const emails = (yield* Config.string("ACCESS_ALLOWED_EMAILS").pipe(
Config.withDefault(""),
))
.split(",")
.map((email) => email.trim())
.filter(Boolean);
if (emails.length === 0) {
return yield* Effect.die(new Error(remedy));
}
return emails;
});
/** The gate itself: an email allow-policy on a self-hosted Access application. */
export const emailAccessGate = (options: {
policyId: string;
applicationId: string;
policyName: string;
applicationName: string;
domain: string;
emails: string[];
}) =>
Effect.gen(function* () {
const allow = yield* Cloudflare.Access.Policy(options.policyId, {
name: options.policyName,
decision: "allow",
include: options.emails.map((email) => ({ email: { email } })),
});
return yield* Cloudflare.Access.Application(options.applicationId, {
type: "self_hosted",
name: options.applicationName,
domain: options.domain,
policies: [allow.policyId],
});
});