/**
 * AWS's hard maximum lifetime of an S3 SigV4 presigned URL — seven days. A
 * requested expiry above this can't be served by a raw presigned link, so the
 * delivery transparently falls back to the persistent-download-URL system.
 */
export const S3_PRESIGNED_URL_MAX_EXPIRATION_SECONDS = 604800;
/**
 * Postgres `integer` column ceiling. Every numeric org setting is stored in an
 * `integer` column, so a value above this overflows the column (a DB error, not
 * a clean validation failure). Used as the hard upper bound for all of them.
 */
export const POSTGRES_INTEGER_MAX = 2147483647;
export const isCorsRegexPattern = (value) => value.length > 2 && value.startsWith('/') && value.endsWith('/');
const CORS_HOST_LABEL_PATTERN = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
const CORS_WILDCARD_ORIGIN_PATTERN = new RegExp(`^(?:(https?):\\/\\/)?\\*\\.(${CORS_HOST_LABEL_PATTERN}(?:\\.${CORS_HOST_LABEL_PATTERN})+)(?::([0-9]{1,5}))?$`);
const escapeRegexPart = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export const getCorsWildcardOriginParts = (value) => {
    const match = value.trim().match(CORS_WILDCARD_ORIGIN_PATTERN);
    if (!match) {
        return null;
    }
    const [, protocol = 'https', hostname, port = null] = match;
    if (port !== null && Number(port) > 65535) {
        return null;
    }
    return {
        protocol: protocol,
        hostname,
        port,
    };
};
export const isCorsWildcardOrigin = (value) => getCorsWildcardOriginParts(value) !== null;
export const getCorsWildcardOriginRegexSource = (value) => {
    const parts = getCorsWildcardOriginParts(value);
    if (!parts) {
        return null;
    }
    const subdomainSource = '.*';
    const portSource = parts.port ? `:${parts.port}` : '';
    return `^${parts.protocol}:\\/\\/${subdomainSource}\\.${escapeRegexPart(parts.hostname)}${portSource}$`;
};
const BROAD_CORS_REGEX_SAMPLE_ORIGINS = [
    'https://malicious.com',
    'http://malicious.com',
    'https://attacker.invalid',
    'https://anything.bad-example.com',
];
export const validateCorsAllowedDomain = (value) => {
    const trimmedValue = value.trim();
    if (trimmedValue.length === 0) {
        return 'CORS allowed origins cannot be empty.';
    }
    if (isCorsRegexPattern(trimmedValue)) {
        try {
            const pattern = trimmedValue.slice(1, -1);
            if (pattern.length > 256) {
                return 'CORS regex patterns must be 256 characters or fewer.';
            }
            const regex = new RegExp(pattern);
            regex.test('');
            const { source } = regex;
            const startsWithOriginProtocol = source.startsWith('^https:\\/\\/') ||
                source.startsWith('^http:\\/\\/') ||
                source.startsWith('^https?:\\/\\/');
            if (!startsWithOriginProtocol || !source.endsWith('$')) {
                return 'CORS regex patterns must be anchored origins, for example /^https:\\/\\/.*\\.example\\.com$/.';
            }
            if (BROAD_CORS_REGEX_SAMPLE_ORIGINS.some((origin) => regex.test(origin))) {
                return 'CORS regex patterns cannot match arbitrary external origins.';
            }
            return null;
        }
        catch {
            return 'CORS regex patterns must be valid JavaScript regular expressions.';
        }
    }
    if (isCorsWildcardOrigin(trimmedValue)) {
        return null;
    }
    if (trimmedValue.includes('*')) {
        return 'CORS wildcard origins must use a leading subdomain wildcard, for example *.example.com or https://*.example.com.';
    }
    try {
        const url = new URL(trimmedValue);
        if (!['http:', 'https:'].includes(url.protocol)) {
            return 'CORS origins must use http or https.';
        }
        if (url.origin !== trimmedValue) {
            return 'CORS origins must not include a path, query string, hash, or trailing slash.';
        }
        return null;
    }
    catch {
        return 'CORS entries must be exact origins like https://app.example.com, wildcard subdomains like *.example.com, or regex patterns like /^https:\\/\\/.*\\.example\\.com$/.';
    }
};
export const validateCorsAllowedDomains = (values) => {
    const seen = new Set();
    for (const value of values) {
        const trimmedValue = value.trim();
        const error = validateCorsAllowedDomain(trimmedValue);
        if (error) {
            return error;
        }
        if (seen.has(trimmedValue)) {
            return 'CORS allowed origins must be unique.';
        }
        seen.add(trimmedValue);
    }
    return null;
};
/**
 * The single source of truth for resolving a stored (raw) org settings record
 * against the instance defaults: an explicit per-org value wins; a value that's
 * `null`, or simply absent (an unset column / no row at all), falls back to the
 * instance default. Used by the backend for both the API response (so the
 * frontend just displays the effective value) and the login flow, so the two
 * can never drift.
 */
export const resolveEffectiveOrganizationSettings = (raw, instanceDefaults) => ({
    oidcLinkingEnabled: raw.oidcLinkingEnabled ?? instanceDefaults.enableOidcLinking,
    oidcToEmailLinkingEnabled: raw.oidcToEmailLinkingEnabled ??
        instanceDefaults.enableOidcToEmailLinking,
    // Opt-in only — no instance default, so an unset value resolves to false.
    supportImpersonationEnabled: raw.supportImpersonationEnabled ?? false,
    // Base is resolved to an effective number (falls back to the env default).
    scheduledDeliveryExpirationSeconds: raw.scheduledDeliveryExpirationSeconds ??
        instanceDefaults.scheduledDeliveryExpirationSeconds,
    // Per-channel overrides are surfaced raw (null = inherit base) so the UI
    // can tell "not set" apart from an explicit value; their fallback to the
    // base happens at delivery time, not here.
    scheduledDeliveryExpirationSecondsEmail: raw.scheduledDeliveryExpirationSecondsEmail ?? null,
    scheduledDeliveryExpirationSecondsSlack: raw.scheduledDeliveryExpirationSecondsSlack ?? null,
    scheduledDeliveryExpirationSecondsMsTeams: raw.scheduledDeliveryExpirationSecondsMsTeams ?? null,
    scheduledDeliveryExpirationSecondsGoogleChat: raw.scheduledDeliveryExpirationSecondsGoogleChat ?? null,
    // Limits resolve to an effective number (fall back to the env default).
    queryLimit: raw.queryLimit ?? instanceDefaults.queryLimit,
    csvCellsLimit: raw.csvCellsLimit ?? instanceDefaults.csvCellsLimit,
    corsAllowedDomains: raw.corsAllowedDomains ?? [],
});
//# sourceMappingURL=organizationSettings.js.map