UNPKG

@withstudiocms/auth-kit

Version:

Utilities for managing authentication

158 lines (157 loc) 4.91 kB
import { createHash } from "node:crypto"; import { resolveSrv } from "node:dns/promises"; import { stringify } from "node:querystring"; const BASE_URL = "http://cdn.libravatar.org/avatar/"; const SECURE_BASE_URL = "https://seccdn.libravatar.org/avatar/"; const SERVICE_BASE = "_avatars._tcp"; const SECURE_SERVICE_BASE = "_avatars-sec._tcp"; const srvHostname = (records) => { if (records.length < 1) { return [null, null]; } if (records.length === 1) { return [records[0].name, records[0].port]; } let priorityRecords = []; let totalWeight = 0; let topPriority = records[0].priority; records.forEach((srvRecord) => { if (srvRecord.priority <= topPriority) { if (srvRecord.priority < topPriority) { topPriority = srvRecord.priority; totalWeight = 0; priorityRecords = []; } totalWeight += srvRecord.weight; if (srvRecord.weight > 0) { priorityRecords.push([totalWeight, srvRecord]); } else { priorityRecords.unshift([0, srvRecord]); } } }); if (priorityRecords.length === 1) { const srvRecord = priorityRecords[0][1]; return [srvRecord.name, srvRecord.port]; } const randomNumber = Math.floor(Math.random() * (totalWeight + 1)); for (let i = 0; i < priorityRecords.length; i++) { const weightedIndex = priorityRecords[i][0]; const target = priorityRecords[i][1]; if (weightedIndex >= randomNumber) { return [target.name, target.port]; } } console.log("There is something wrong with our SRV weight ordering algorithm"); return [null, null]; }; const sanitizedTarget = (targetComponents, https) => { const target = targetComponents[0]; const port = targetComponents[1]; if (target === null || port === null || Number.isNaN(port)) { return null; } if (port < 1 || port > 65535) { return null; } if (target.search(/^[0-9a-zA-Z\-.]+$/) === -1) { return null; } if (target && (https && port !== 443 || !https && port !== 80)) { return `${target}:${port}`; } return target; }; const parseUserIdentity = (email, openid) => { let hash = null; let domain = null; if (email != null) { const lowercaseValue = email.trim().toLowerCase(); const emailParts = lowercaseValue.split("@"); if (emailParts.length > 1) { domain = emailParts[emailParts.length - 1]; hash = createHash("md5").update(lowercaseValue).digest("hex"); } } else if (openid != null) { try { const parsedUrl = new URL(openid); let normalizedUrl = parsedUrl.protocol.toLowerCase(); normalizedUrl += "//"; if (parsedUrl.username || parsedUrl.password) { const auth = parsedUrl.username + (parsedUrl.password ? `:${parsedUrl.password}` : ""); normalizedUrl += `${auth}@`; } normalizedUrl += parsedUrl.hostname.toLowerCase() + parsedUrl.pathname; domain = parsedUrl.hostname.toLowerCase(); hash = createHash("sha256").update(normalizedUrl).digest("hex"); } catch (_error) { return { hash: null, domain: null }; } } return { hash, domain }; }; const serviceName = (domain, https) => { if (domain) { return `${https ? SECURE_SERVICE_BASE : SERVICE_BASE}.${domain}`; } return null; }; const composeAvatarUrl = (delegationServer, avatarHash, queryString, https) => { let baseUrl = https ? SECURE_BASE_URL : BASE_URL; if (delegationServer) { baseUrl = `http${https ? "s" : ""}://${delegationServer}/avatar/`; } return baseUrl + avatarHash + queryString; }; const getDelegationServer = async (domain, https) => { if (!domain) { return null; } const service = serviceName(domain, https); if (!service) { return null; } try { const addresses = await resolveSrv(service); return sanitizedTarget(srvHostname(addresses), https); } catch (_error) { return null; } }; const getAvatarUrl = async (options) => { const identity = parseUserIdentity(options.email, options.openid); const hash = identity.hash; const domain = identity.domain; const https = options.https || false; if (!hash) { throw new Error("An email or an OpenID must be provided."); } const queryOptions = { ...options }; delete queryOptions.email; delete queryOptions.openid; delete queryOptions.https; const queryData = stringify(queryOptions); const query = queryData ? `?${queryData}` : ""; const delegationServer = await getDelegationServer(domain, https); return composeAvatarUrl(delegationServer, hash, query, https); }; var libravatar_default = { getAvatarUrl, // Export utility functions for testing sanitizedTarget, srvHostname, parseUserIdentity, serviceName, composeAvatarUrl, getDelegationServer }; export { composeAvatarUrl, libravatar_default as default, getAvatarUrl, getDelegationServer, parseUserIdentity, sanitizedTarget, serviceName, srvHostname };