File size: 5,612 Bytes
002f18c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env node

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";

const EXPECTED_RATE_LIMITS = new Map([
  ["API_PREAUTH_RATE_LIMITER", { limit: 20, period: 60 }],
  ["API_RATE_LIMITER", { limit: 60, period: 60 }],
]);

export class DeploymentPreflightError extends Error {
  constructor(message) {
    super(message);
    this.name = "DeploymentPreflightError";
  }
}

export function validateDeploymentEnvironment(env = {}) {
  const accessSecret = env.AUREON_WORKER_ACCESS_SECRET;
  if (
    typeof accessSecret !== "string"
    || !accessSecret
    || accessSecret !== accessSecret.trim()
    || Buffer.byteLength(accessSecret, "utf8") < 32
  ) {
    throw new DeploymentPreflightError(
      "AUREON_WORKER_ACCESS_SECRET must be trimmed and at least 32 UTF-8 bytes.",
    );
  }

  const rawOrigins = env.AUREON_ALLOWED_ORIGINS;
  if (typeof rawOrigins !== "string" || !rawOrigins) {
    throw new DeploymentPreflightError(
      "AUREON_ALLOWED_ORIGINS must contain at least one exact HTTPS origin.",
    );
  }

  const origins = rawOrigins.split(",");
  const uniqueOrigins = new Set();
  for (let index = 0; index < origins.length; index += 1) {
    const candidate = origins[index];
    if (!candidate || candidate !== candidate.trim() || candidate === "*") {
      throw new DeploymentPreflightError(
        `AUREON_ALLOWED_ORIGINS entry ${index + 1} is not an exact HTTPS origin.`,
      );
    }
    try {
      const parsed = new URL(candidate);
      if (parsed.protocol !== "https:" || parsed.origin !== candidate) {
        throw new Error("not canonical");
      }
    } catch {
      throw new DeploymentPreflightError(
        `AUREON_ALLOWED_ORIGINS entry ${index + 1} is not an exact HTTPS origin.`,
      );
    }
    if (uniqueOrigins.has(candidate)) {
      throw new DeploymentPreflightError(
        `AUREON_ALLOWED_ORIGINS entry ${index + 1} is duplicated.`,
      );
    }
    uniqueOrigins.add(candidate);
  }

  const paidProviders = env.AUREON_ALLOW_PAID_PROVIDERS;
  if (paidProviders !== undefined && paidProviders !== "true") {
    throw new DeploymentPreflightError(
      "AUREON_ALLOW_PAID_PROVIDERS must be omitted or exactly true.",
    );
  }

  return {
    originCount: uniqueOrigins.size,
    paidProvidersEnabled: paidProviders === "true",
  };
}

export function validateWranglerRateLimits(config) {
  if (!config || typeof config !== "object" || Array.isArray(config)) {
    throw new DeploymentPreflightError("Wrangler configuration must be an object.");
  }
  if (
    config.vars
    && Object.prototype.hasOwnProperty.call(config.vars, "AUREON_WORKER_ACCESS_SECRET")
  ) {
    throw new DeploymentPreflightError(
      "AUREON_WORKER_ACCESS_SECRET must be a provider secret, not a Wrangler vars value.",
    );
  }

  if (!Array.isArray(config.ratelimits)) {
    throw new DeploymentPreflightError("Wrangler rate-limit bindings are missing.");
  }

  for (const [name, expected] of EXPECTED_RATE_LIMITS) {
    const matches = config.ratelimits.filter((entry) => entry?.name === name);
    if (matches.length !== 1) {
      throw new DeploymentPreflightError(
        `Wrangler must define exactly one ${name} rate-limit binding.`,
      );
    }
    const binding = matches[0];
    if (typeof binding.namespace_id !== "string" || !binding.namespace_id.trim()) {
      throw new DeploymentPreflightError(`${name} must have a namespace_id.`);
    }
    if (
      binding.simple?.limit !== expected.limit
      || binding.simple?.period !== expected.period
    ) {
      throw new DeploymentPreflightError(
        `${name} must use limit ${expected.limit} and period ${expected.period}.`,
      );
    }
  }

  return { bindingCount: EXPECTED_RATE_LIMITS.size };
}

export function loadWranglerConfig(configPath) {
  let source;
  try {
    source = fs.readFileSync(configPath, "utf8").replace(/^\uFEFF/, "");
  } catch {
    throw new DeploymentPreflightError("Wrangler configuration could not be read.");
  }
  try {
    return JSON.parse(source);
  } catch {
    throw new DeploymentPreflightError(
      "Wrangler configuration must remain JSON-compatible JSONC for offline validation.",
    );
  }
}

export function runPreflight({ env = process.env, configPath } = {}) {
  const environment = validateDeploymentEnvironment(env);
  const rateLimits = validateWranglerRateLimits(loadWranglerConfig(configPath));
  return { ...environment, ...rateLimits };
}

const modulePath = fileURLToPath(import.meta.url);
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
if (invokedPath && pathToFileURL(invokedPath).href === import.meta.url) {
  const defaultConfigPath = path.resolve(path.dirname(modulePath), "..", "wrangler.jsonc");
  try {
    const result = runPreflight({
      env: process.env,
      configPath: process.argv[2] ? path.resolve(process.argv[2]) : defaultConfigPath,
    });
    console.log(
      `Cloudflare deployment security preflight passed: ${result.originCount} origin(s), `
      + `${result.bindingCount} rate-limit binding(s), paid providers `
      + `${result.paidProvidersEnabled ? "enabled" : "disabled"}.`,
    );
  } catch (error) {
    const message = error instanceof DeploymentPreflightError
      ? error.message
      : "unexpected validation error";
    console.error(`Cloudflare deployment security preflight failed: ${message}`);
    process.exitCode = 1;
  }
}