better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 7.07 kB
Source Map (JSON)
{"version":3,"file":"url.mjs","names":["url"],"sources":["../../src/utils/url.ts"],"sourcesContent":["import { env } from \"@better-auth/core/env\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\n\nfunction checkHasPath(url: string): boolean {\n\ttry {\n\t\tconst parsedUrl = new URL(url);\n\t\tconst pathname = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"/\";\n\t\treturn pathname !== \"/\";\n\t} catch {\n\t\tthrow new BetterAuthError(\n\t\t\t`Invalid base URL: ${url}. Please provide a valid base URL.`,\n\t\t);\n\t}\n}\n\nfunction assertHasProtocol(url: string): void {\n\ttry {\n\t\tconst parsedUrl = new URL(url);\n\t\tif (parsedUrl.protocol !== \"http:\" && parsedUrl.protocol !== \"https:\") {\n\t\t\tthrow new BetterAuthError(\n\t\t\t\t`Invalid base URL: ${url}. URL must include 'http://' or 'https://'`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof BetterAuthError) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new BetterAuthError(\n\t\t\t`Invalid base URL: ${url}. Please provide a valid base URL.`,\n\t\t\tString(error),\n\t\t);\n\t}\n}\n\nfunction withPath(url: string, path = \"/api/auth\") {\n\tassertHasProtocol(url);\n\n\tconst hasPath = checkHasPath(url);\n\tif (hasPath) {\n\t\treturn url;\n\t}\n\n\tconst trimmedUrl = url.replace(/\\/+$/, \"\");\n\n\tif (!path || path === \"/\") {\n\t\treturn trimmedUrl;\n\t}\n\n\tpath = path.startsWith(\"/\") ? path : `/${path}`;\n\treturn `${trimmedUrl}${path}`;\n}\n\nfunction validateProxyHeader(header: string, type: \"host\" | \"proto\"): boolean {\n\tif (!header || header.trim() === \"\") {\n\t\treturn false;\n\t}\n\n\tif (type === \"proto\") {\n\t\t// Only allow http and https protocols\n\t\treturn header === \"http\" || header === \"https\";\n\t}\n\n\tif (type === \"host\") {\n\t\tconst suspiciousPatterns = [\n\t\t\t/\\.\\./, // Path traversal\n\t\t\t/\\0/, // Null bytes\n\t\t\t/[\\s]/, // Whitespace (except legitimate spaces that should be trimmed)\n\t\t\t/^[.]/, // Starting with dot\n\t\t\t/[<>'\"]/, // HTML/script injection characters\n\t\t\t/javascript:/i, // Protocol injection\n\t\t\t/file:/i, // File protocol\n\t\t\t/data:/i, // Data protocol\n\t\t];\n\n\t\tif (suspiciousPatterns.some((pattern) => pattern.test(header))) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Basic hostname validation (allows localhost, IPs, and domains with ports)\n\t\t// This is a simple check, not exhaustive RFC validation\n\t\tconst hostnameRegex =\n\t\t\t/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(:[0-9]{1,5})?$/;\n\n\t\t// Also allow IPv4 addresses\n\t\tconst ipv4Regex = /^(\\d{1,3}\\.){3}\\d{1,3}(:[0-9]{1,5})?$/;\n\n\t\t// Also allow IPv6 addresses in brackets\n\t\tconst ipv6Regex = /^\\[[0-9a-fA-F:]+\\](:[0-9]{1,5})?$/;\n\n\t\t// Allow localhost variations\n\t\tconst localhostRegex = /^localhost(:[0-9]{1,5})?$/i;\n\n\t\treturn (\n\t\t\thostnameRegex.test(header) ||\n\t\t\tipv4Regex.test(header) ||\n\t\t\tipv6Regex.test(header) ||\n\t\t\tlocalhostRegex.test(header)\n\t\t);\n\t}\n\n\treturn false;\n}\n\nexport function getBaseURL(\n\turl?: string,\n\tpath?: string,\n\trequest?: Request,\n\tloadEnv?: boolean,\n\ttrustedProxyHeaders?: boolean | undefined,\n) {\n\tif (url) {\n\t\treturn withPath(url, path);\n\t}\n\n\tif (loadEnv !== false) {\n\t\tconst fromEnv =\n\t\t\tenv.BETTER_AUTH_URL ||\n\t\t\tenv.NEXT_PUBLIC_BETTER_AUTH_URL ||\n\t\t\tenv.PUBLIC_BETTER_AUTH_URL ||\n\t\t\tenv.NUXT_PUBLIC_BETTER_AUTH_URL ||\n\t\t\tenv.NUXT_PUBLIC_AUTH_URL ||\n\t\t\t(env.BASE_URL !== \"/\" ? env.BASE_URL : undefined);\n\n\t\tif (fromEnv) {\n\t\t\treturn withPath(fromEnv, path);\n\t\t}\n\t}\n\n\tconst fromRequest = request?.headers.get(\"x-forwarded-host\");\n\tconst fromRequestProto = request?.headers.get(\"x-forwarded-proto\");\n\tif (fromRequest && fromRequestProto && trustedProxyHeaders) {\n\t\tif (\n\t\t\tvalidateProxyHeader(fromRequestProto, \"proto\") &&\n\t\t\tvalidateProxyHeader(fromRequest, \"host\")\n\t\t) {\n\t\t\ttry {\n\t\t\t\treturn withPath(`${fromRequestProto}://${fromRequest}`, path);\n\t\t\t} catch (_error) {}\n\t\t}\n\t}\n\n\tif (request) {\n\t\tconst url = getOrigin(request.url);\n\t\tif (!url) {\n\t\t\tthrow new BetterAuthError(\n\t\t\t\t\"Could not get origin from request. Please provide a valid base URL.\",\n\t\t\t);\n\t\t}\n\t\treturn withPath(url, path);\n\t}\n\n\tif (typeof window !== \"undefined\" && window.location) {\n\t\treturn withPath(window.location.origin, path);\n\t}\n\treturn undefined;\n}\n\nexport function getOrigin(url: string) {\n\ttry {\n\t\tconst parsedUrl = new URL(url);\n\t\t// For custom URL schemes (like exp://), the origin property returns the string \"null\"\n\t\t// instead of null. We need to handle this case and return null so the fallback logic works.\n\t\treturn parsedUrl.origin === \"null\" ? null : parsedUrl.origin;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getProtocol(url: string) {\n\ttry {\n\t\tconst parsedUrl = new URL(url);\n\t\treturn parsedUrl.protocol;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getHost(url: string) {\n\ttry {\n\t\tconst parsedUrl = new URL(url);\n\t\treturn parsedUrl.host;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":";;;;AAGA,SAAS,aAAa,KAAsB;AAC3C,KAAI;AAGH,UAFkB,IAAI,IAAI,IAAI,CACH,SAAS,QAAQ,QAAQ,GAAG,IAAI,SACvC;SACb;AACP,QAAM,IAAI,gBACT,qBAAqB,IAAI,oCACzB;;;AAIH,SAAS,kBAAkB,KAAmB;AAC7C,KAAI;EACH,MAAM,YAAY,IAAI,IAAI,IAAI;AAC9B,MAAI,UAAU,aAAa,WAAW,UAAU,aAAa,SAC5D,OAAM,IAAI,gBACT,qBAAqB,IAAI,4CACzB;UAEM,OAAO;AACf,MAAI,iBAAiB,gBACpB,OAAM;AAEP,QAAM,IAAI,gBACT,qBAAqB,IAAI,qCACzB,OAAO,MAAM,CACb;;;AAIH,SAAS,SAAS,KAAa,OAAO,aAAa;AAClD,mBAAkB,IAAI;AAGtB,KADgB,aAAa,IAAI,CAEhC,QAAO;CAGR,MAAM,aAAa,IAAI,QAAQ,QAAQ,GAAG;AAE1C,KAAI,CAAC,QAAQ,SAAS,IACrB,QAAO;AAGR,QAAO,KAAK,WAAW,IAAI,GAAG,OAAO,IAAI;AACzC,QAAO,GAAG,aAAa;;AAGxB,SAAS,oBAAoB,QAAgB,MAAiC;AAC7E,KAAI,CAAC,UAAU,OAAO,MAAM,KAAK,GAChC,QAAO;AAGR,KAAI,SAAS,QAEZ,QAAO,WAAW,UAAU,WAAW;AAGxC,KAAI,SAAS,QAAQ;AAYpB,MAX2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAEsB,MAAM,YAAY,QAAQ,KAAK,OAAO,CAAC,CAC7D,QAAO;AAiBR,SAXC,8GAYc,KAAK,OAAO,IATT,wCAUP,KAAK,OAAO,IAPL,oCAQP,KAAK,OAAO,IALA,6BAMP,KAAK,OAAO;;AAI7B,QAAO;;AAGR,SAAgB,WACf,KACA,MACA,SACA,SACA,qBACC;AACD,KAAI,IACH,QAAO,SAAS,KAAK,KAAK;AAG3B,KAAI,YAAY,OAAO;EACtB,MAAM,UACL,IAAI,mBACJ,IAAI,+BACJ,IAAI,0BACJ,IAAI,+BACJ,IAAI,yBACH,IAAI,aAAa,MAAM,IAAI,WAAW;AAExC,MAAI,QACH,QAAO,SAAS,SAAS,KAAK;;CAIhC,MAAM,cAAc,SAAS,QAAQ,IAAI,mBAAmB;CAC5D,MAAM,mBAAmB,SAAS,QAAQ,IAAI,oBAAoB;AAClE,KAAI,eAAe,oBAAoB,qBACtC;MACC,oBAAoB,kBAAkB,QAAQ,IAC9C,oBAAoB,aAAa,OAAO,CAExC,KAAI;AACH,UAAO,SAAS,GAAG,iBAAiB,KAAK,eAAe,KAAK;WACrD,QAAQ;;AAInB,KAAI,SAAS;EACZ,MAAMA,QAAM,UAAU,QAAQ,IAAI;AAClC,MAAI,CAACA,MACJ,OAAM,IAAI,gBACT,sEACA;AAEF,SAAO,SAASA,OAAK,KAAK;;AAG3B,KAAI,OAAO,WAAW,eAAe,OAAO,SAC3C,QAAO,SAAS,OAAO,SAAS,QAAQ,KAAK;;AAK/C,SAAgB,UAAU,KAAa;AACtC,KAAI;EACH,MAAM,YAAY,IAAI,IAAI,IAAI;AAG9B,SAAO,UAAU,WAAW,SAAS,OAAO,UAAU;SAC/C;AACP,SAAO;;;AAIT,SAAgB,YAAY,KAAa;AACxC,KAAI;AAEH,SADkB,IAAI,IAAI,IAAI,CACb;SACV;AACP,SAAO;;;AAIT,SAAgB,QAAQ,KAAa;AACpC,KAAI;AAEH,SADkB,IAAI,IAAI,IAAI,CACb;SACV;AACP,SAAO"}