Files
obsidian-livesync/src/integrations/cloudflare/settings.ts
T

50 lines
2.0 KiB
TypeScript

/** The provider identifier persisted in a P2P profile for Cloudflare TURN. */
export const CLOUDFLARE_TURN_TYPE = "CF" as const;
/** The lifetime requested from Cloudflare for each issued credential set. */
export const CLOUDFLARE_TURN_CREDENTIAL_TTL_SECONDS = 86_400 as const;
/** The Cloudflare TURN credential-generation endpoint. */
export const CLOUDFLARE_TURN_CREDENTIAL_ENDPOINT = "https://rtc.live.cloudflare.com/v1/turn/keys" as const;
/** A Cloudflare TURN configuration. */
export interface CloudflareTurnConfiguration {
readonly turnKeyId: string;
readonly apiToken: string;
}
// TURN Key IDs are inserted into one fixed URL path. Keep the accepted set
// deliberately narrower than URI escaping so a configuration cannot alter
// the request path or add a query string.
const TURN_KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,255}$/;
// RFC 6750's b64token grammar, including optional trailing padding. This
// also excludes whitespace and control characters from the Authorization
// header without exposing the token in a validation message.
const BEARER_TOKEN_PATTERN = /^[A-Za-z0-9._~+/-]+={0,2}$/;
const MAX_BEARER_TOKEN_LENGTH = 4_096;
/**
* Returns a safe validation message for a Cloudflare TURN configuration.
* The result never includes the supplied Key ID or API token.
*/
export function validateCloudflareTurnConfiguration(value: CloudflareTurnConfiguration): string | undefined {
const turnKeyId = value.turnKeyId;
if (typeof turnKeyId !== "string" || turnKeyId.length === 0) {
return "Enter a TURN Key ID.";
}
if (!TURN_KEY_ID_PATTERN.test(turnKeyId)) {
return "TURN Key ID contains unsupported characters.";
}
const apiToken = value.apiToken;
if (typeof apiToken !== "string" || apiToken.length === 0) {
return "Enter a TURN Key API Token.";
}
if (apiToken.length > MAX_BEARER_TOKEN_LENGTH || !BEARER_TOKEN_PATTERN.test(apiToken)) {
return "TURN Key API Token must use Bearer token syntax.";
}
return undefined;
}