UNPKG

@better-auth-ui/core

Version:

Authentication components and data utilities for [Better Auth](https://better-auth.com), available for React and Solid.

169 lines (146 loc) 4.86 kB
const ABSOLUTE_HTTP_URL = /^https?:\/\//i export const REAUTHENTICATION_QUERY_PARAM = "reauthenticate" /** * Build a callback URL from an optional origin, a configured base path, and a * view path. * * Separators are normalized so custom paths work whether callers include * leading or trailing slashes. */ export function getViewURL( baseURL: string, basePath: string, viewPath: string ): string { const origin = baseURL.replace(/\/+$/, "") const path = [basePath, viewPath] .map((segment) => segment.replace(/^\/+|\/+$/g, "")) .filter(Boolean) .join("/") return `${origin}/${path}` } /** * Add the current post-authentication destination to an internal auth link. */ export function getAuthLinkURL(href: string, redirectTo: string): string { const hashIndex = href.indexOf("#") const hash = hashIndex === -1 ? "" : href.slice(hashIndex) const hrefWithoutHash = hashIndex === -1 ? href : href.slice(0, hashIndex) const queryIndex = hrefWithoutHash.indexOf("?") const pathname = queryIndex === -1 ? hrefWithoutHash : hrefWithoutHash.slice(0, queryIndex) const searchParams = new URLSearchParams( queryIndex === -1 ? "" : hrefWithoutHash.slice(queryIndex + 1) ) searchParams.set("redirectTo", redirectTo) return `${pathname}?${searchParams}${hash}` } /** Build a sign-in URL that returns to the exact current page after authentication. */ export function getReauthenticationSignInURL( currentURL: URL, signInPath: string ): string { const signInURL = new URL(signInPath, currentURL.origin) signInURL.searchParams.set(REAUTHENTICATION_QUERY_PARAM, "true") signInURL.searchParams.set( "redirectTo", `${currentURL.pathname}${currentURL.search}${currentURL.hash}` ) return `${signInURL.pathname}${signInURL.search}${signInURL.hash}` } /** Return whether the current sign-in URL was opened for reauthentication. */ export function isReauthenticationSignInURL(currentURL: URL): boolean { return currentURL.searchParams.get(REAUTHENTICATION_QUERY_PARAM) === "true" } function hasUnsafeRedirectCharacters(value: string): boolean { for (const character of value) { const codePoint = character.codePointAt(0) if ( character === "\\" || codePoint === undefined || codePoint <= 31 || codePoint === 127 ) { return true } } return false } /** * Normalize a redirect target to a same-origin path. * * Root-relative paths and same-origin HTTP(S) URLs are accepted. Invalid, * cross-origin, protocol-relative, and non-HTTP targets fall back to `/`. * * @param redirectTo - Requested redirect target * @param origin - Origin used to validate and normalize the target * @returns A same-origin path including its query string and hash */ export function getSafeRedirectTo( redirectTo: string | null | undefined, origin: string ): string { if (!redirectTo || hasUnsafeRedirectCharacters(redirectTo)) return "/" const target = redirectTo.trim() if ( !target || target.startsWith("//") || (!target.startsWith("/") && !ABSOLUTE_HTTP_URL.test(target)) ) { return "/" } try { const baseURL = new URL(origin) const targetURL = new URL(target, baseURL) if ( targetURL.origin !== baseURL.origin || targetURL.username || targetURL.password ) { return "/" } return `${targetURL.pathname}${targetURL.search}${targetURL.hash}` } catch { return "/" } } export type AuthRedirectAction = | { type: "redirect"; to: string } | { type: "signIn"; to: string } /** * Resolve the next action for the authenticated redirect view. * * Signed-in users continue to the validated target. Signed-out users are sent * to sign in with the current redirect-view URL preserved, allowing the view * to perform a full-page redirect after authentication. * * @param currentURL - Current redirect-view URL * @param authenticated - Whether the current user has a session * @param signInPath - Same-origin path to the sign-in view * @returns The redirect or sign-in action to perform */ export function getAuthRedirectAction( currentURL: URL, authenticated: boolean, signInPath: string ): AuthRedirectAction { const requestedTarget = getSafeRedirectTo( currentURL.searchParams.get("redirectTo"), currentURL.origin ) const targetURL = new URL(requestedTarget, currentURL.origin) const redirectTo = targetURL.pathname === currentURL.pathname ? "/" : requestedTarget if (authenticated) { return { type: "redirect", to: redirectTo } } const signInURL = new URL(signInPath, currentURL.origin) signInURL.searchParams.set( "redirectTo", `${currentURL.pathname}${currentURL.search}${currentURL.hash}` ) return { type: "signIn", to: `${signInURL.pathname}${signInURL.search}${signInURL.hash}` } }