mirror of
https://github.com/better-auth/better-auth.git
synced 2026-08-21 21:16:20 -05:00
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import { X509Certificate } from "node:crypto";
|
|
import { getHostname } from "tldts";
|
|
|
|
/**
|
|
* Safely parses a value that might be a JSON string or already a parsed object.
|
|
* This handles cases where ORMs like Drizzle might return already parsed objects
|
|
* instead of JSON strings from TEXT/JSON columns.
|
|
*
|
|
* @param value - The value to parse (string, object, null, or undefined)
|
|
* @returns The parsed object or null
|
|
* @throws Error if string parsing fails
|
|
*/
|
|
export function safeJsonParse<T>(
|
|
value: string | T | null | undefined,
|
|
): T | null {
|
|
if (!value) return null;
|
|
|
|
if (typeof value === "object") {
|
|
return value as T;
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
try {
|
|
return JSON.parse(value) as T;
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if a domain matches any domain in a comma-separated list.
|
|
*/
|
|
export const domainMatches = (searchDomain: string, domainList: string) => {
|
|
const search = searchDomain.toLowerCase();
|
|
const domains = domainList
|
|
.split(",")
|
|
.map((d) => d.trim().toLowerCase())
|
|
.filter(Boolean);
|
|
return domains.some((d) => search === d || search.endsWith(`.${d}`));
|
|
};
|
|
|
|
/**
|
|
* Validates email domain against allowed domain(s).
|
|
* Supports comma-separated domains for multi-domain SSO.
|
|
*/
|
|
export const validateEmailDomain = (email: string, domain: string) => {
|
|
const emailDomain = email.split("@")[1]?.toLowerCase();
|
|
if (!emailDomain || !domain) {
|
|
return false;
|
|
}
|
|
return domainMatches(emailDomain, domain);
|
|
};
|
|
|
|
export function parseCertificate(certPem: string) {
|
|
// SAML metadata X509Certificate elements contain raw base64 without PEM headers,
|
|
// but users may also provide full PEM-formatted certificates. Normalize to PEM.
|
|
const normalized = certPem.includes("-----BEGIN")
|
|
? certPem
|
|
: `-----BEGIN CERTIFICATE-----\n${certPem}\n-----END CERTIFICATE-----`;
|
|
|
|
const cert = new X509Certificate(normalized);
|
|
|
|
return {
|
|
fingerprintSha256: cert.fingerprint256,
|
|
notBefore: cert.validFrom,
|
|
notAfter: cert.validTo,
|
|
publicKeyAlgorithm:
|
|
cert.publicKey.asymmetricKeyType?.toUpperCase() || "UNKNOWN",
|
|
};
|
|
}
|
|
|
|
export function getHostnameFromDomain(domain: string): string | null {
|
|
return getHostname(domain) || null;
|
|
}
|
|
|
|
export function maskClientId(clientId: string): string {
|
|
if (clientId.length <= 4) {
|
|
return "****";
|
|
}
|
|
return `****${clientId.slice(-4)}`;
|
|
}
|