Skip to the content

Capsid menu

app/lib/operator/auth.server.ts

67 lines and 2 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of auth.server.ts
FunctionLinesComplexityCoveredCRAPMutation
authenticateOperator21 to 577No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
meterOperator60 to 823No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code

Imports

It imports app/lib/bearer.server.ts, app/lib/rate-limit.mjs. Imported by app/lib/media/backup.server.ts, app/lib/operator/api.server.ts, app/lib/operator/sync-tools.server.ts, app/lib/operator/upload-media.server.ts, app/routes/api.operator.ts.

Source

// The token is the whole boundary: compared in constant time, and never echoed, logged or put in an error.

import { safeEqual } from "@dustinedwards/site-runtime/compare";

import { tokenLabel } from "~/lib/bearer.server";
import { limitHit } from "~/lib/rate-limit.mjs";

const OPERATOR_RATE_LIMIT = 30;
const OPERATOR_RATE_PERIOD_SECONDS = 60;

const MIN_TOKEN_LENGTH = 32;

export type OperatorEnv = Env & { OPERATOR_TOKEN?: string };

type AuthResult =
  | { ok: true; id: string }
  | { ok: false; status: number; error: string; retryAfter?: number };

// Authenticate FIRST: the limiter is keyed to a proven identity, so an anonymous flood cannot spend a
// real operator's budget or reach a Durable Object.
export async function authenticateOperator(
  env: OperatorEnv,
  request: Request,
): Promise<AuthResult> {
  const configured = env.OPERATOR_TOKEN;

  // No secret set means the path is closed, not open to everyone.
  if (!configured || configured.length < MIN_TOKEN_LENGTH) {
    return {
      ok: false,
      status: 503,
      error:
        "The operator publish path is not configured on this deployment. " +
        "Set the OPERATOR_TOKEN wrangler secret to enable it.",
    };
  }

  const header = request.headers.get("authorization") ?? "";
  const presented = /^Bearer\s+(.+)$/i.exec(header.trim())?.[1] ?? "";

  // Refused on length BEFORE the hash: `safeEqual` hashes both operands, which an anonymous caller
  // could otherwise request in any size. Twice the length, not equal, so it is not a length oracle.
  if (presented.length > configured.length * 2) {
    return { ok: false, status: 401, error: "Invalid or missing bearer token." };
  }

  // Still compared when absent, so a missing header and a wrong token take the same path and time
  // (an empty side is refused by `safeEqual` after both hashes, never before them).
  const valid = await safeEqual(presented, configured);
  if (!valid) {
    return { ok: false, status: 401, error: "Invalid or missing bearer token." };
  }

  const id = tokenLabel(presented);

  return { ok: true, id };
}

// Separate from authentication so reading the describe call does not spend publish budget.
export async function meterOperator(
  env: OperatorEnv,
  id: string,
): Promise<AuthResult> {
  const verdict = await limitHit(env, `op:${id}`, OPERATOR_RATE_LIMIT, OPERATOR_RATE_PERIOD_SECONDS);
  if (verdict.status === "unavailable") {
    return {
      ok: false,
      status: 503,
      error: "Rate limiting is unavailable, so the operator path is disabled.",
    };
  }
  if (verdict.status === "limited") {
    return {
      ok: false,
      status: 429,
      error: `Rate limit: ${OPERATOR_RATE_LIMIT} requests per ${OPERATOR_RATE_PERIOD_SECONDS} seconds.`,
      retryAfter: verdict.retryAfterSeconds,
    };
  }

  return { ok: true, id };
}