ts / dist / esm / gate.js
523 words
  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
import { configError, domainDenied } from './errors.js';
import { get, paginate } from './http.js';
/** Lower-case, trimmed, no trailing dots; undefined for blank input. */
export const normalizeDomain = (domain) => {
    const name = domain?.trim().toLowerCase().replace(/\.+$/, '');
    return name ? name : undefined;
};
const patternMatches = (pattern, name) => {
    const normalized = normalizeDomain(pattern);
    if (!normalized)
        return false;
    if (normalized.startsWith('*.')) {
        const suffix = normalized.slice(2);
        return name === suffix || name.endsWith(`.${suffix}`);
    }
    return normalized === name;
};
const matchPattern = (patterns, name) => patterns.find((pattern) => patternMatches(pattern, name));
const decide = (rules, domain) => {
    const name = normalizeDomain(domain);
    if (!name)
        return rules.allowedDomains ? { domain: null, matchedPattern: null } : undefined;
    const denied = rules.deniedDomains && matchPattern(rules.deniedDomains, name);
    if (denied)
        return { domain: name, matchedPattern: denied };
    if (rules.allowedDomains && !matchPattern(rules.allowedDomains, name)) {
        return { domain: name, matchedPattern: null };
    }
    return undefined;
};
const fetchRegistrationIndex = async (client) => {
    const registrations = await paginate(client, '/domains/registrations', 'registrations', {
        query: { limit: 100 },
    });
    const index = new Map();
    for (const { id, domain } of registrations) {
        const name = normalizeDomain(domain);
        if (id && name)
            index.set(String(id), name);
    }
    return index;
};
const fetchRegistrationDomain = async (client, id) => {
    try {
        const { registration } = await get(client, '/domains/registrations/{id}', { params: { id } });
        return normalizeDomain(registration?.domain);
    }
    catch {
        return undefined;
    }
};
const resolveRegistration = async (client, gate, id) => {
    const cached = gate.registrations.get(id);
    if (cached)
        return cached;
    const domain = await fetchRegistrationDomain(client, id);
    if (domain)
        gate.registrations.set(id, domain);
    return domain;
};
/**
 * Return a copy of CLIENT with a domain gate attached. Eagerly builds the
 * registration id → domain cache, which requires an authenticated client.
 */
export const attachGate = async (client, rules) => {
    if (!client.apiKey?.trim()) {
        throw configError('Domain gate options require an authenticated client (apiKey)', rules);
    }
    // undefined and [] differ: undefined means "no allow-list", [] means
    // "allow nothing" — so copy the lists without coercing either.
    const gate = {
        rules: {
            deniedDomains: rules.deniedDomains ? [...rules.deniedDomains] : undefined,
            allowedDomains: rules.allowedDomains ? [...rules.allowedDomains] : undefined,
        },
        registrations: await fetchRegistrationIndex(client),
    };
    return Object.freeze({ ...client, gate });
};
/**
 * Re-fetch the registration cache of CLIENT's gate (for long-lived clients
 * that registered domains after creation). Returns the refreshed map, or
 * undefined when the client has no gate.
 */
export const refreshGate = async (client) => {
    const { gate } = client;
    if (!gate)
        return undefined;
    const fresh = await fetchRegistrationIndex(client);
    gate.registrations.clear();
    for (const [id, domain] of fresh)
        gate.registrations.set(id, domain);
    return gate.registrations;
};
/**
 * Assert that the mutation OP (e.g. "POST /domains/registrations") may touch
 * TARGET, throwing a domainDenied error otherwise. A no-op for clients
 * without a gate. Registration ids are resolved through the gate's cache,
 * with a miss falling back to a single-registration fetch.
 */
export const checkGate = async (client, op, target) => {
    const { gate } = client;
    if (!gate)
        return;
    const domain = 'domain' in target
        ? (target.domain ?? undefined)
        : target.registrationId == null
            ? undefined
            : await resolveRegistration(client, gate, String(target.registrationId));
    const denial = decide(gate.rules, domain);
    if (denial)
        throw domainDenied(denial.domain, denial.matchedPattern, op);
};

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/ts.git