(path?: string)
| 8 | * @returns Secure URL (HTTPS) or development URL (HTTP localhost) |
| 9 | */ |
| 10 | export function getSecureAppUrl(path?: string): string { |
| 11 | const appUrl = process.env.APP_URL || '' |
| 12 | |
| 13 | if (!appUrl) { |
| 14 | throw new Error('APP_URL environment variable is not configured') |
| 15 | } |
| 16 | |
| 17 | // Validate that APP_URL is a well-formed URL (e.g. catches bare "example.com" without a protocol) |
| 18 | let urlObj: URL |
| 19 | try { |
| 20 | urlObj = new URL(appUrl) |
| 21 | } catch { |
| 22 | throw new Error(`APP_URL environment variable is not a valid URL: "${appUrl}"`) |
| 23 | } |
| 24 | |
| 25 | const isLocalhost = |
| 26 | urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1' || urlObj.hostname === '[::1]' || urlObj.hostname === '0.0.0.0' |
| 27 | |
| 28 | // If URL is HTTP and NOT localhost, convert to HTTPS for security. |
| 29 | // Keep HTTP for localhost/development URLs to avoid issues with self-signed certs. |
| 30 | if (urlObj.protocol === 'http:' && !isLocalhost) { |
| 31 | urlObj.protocol = 'https:' |
| 32 | const newUrlString = urlObj.toString().replace(/\/$/, '') |
| 33 | logger.warn( |
| 34 | `APP_URL uses insecure HTTP protocol for non-localhost URL. ` + |
| 35 | `Automatically converting to HTTPS for security. ` + |
| 36 | `Please update APP_URL to use HTTPS: ${newUrlString}` |
| 37 | ) |
| 38 | } |
| 39 | |
| 40 | // Always strip trailing slash for consistency, whether or not a path is appended |
| 41 | const secureUrl = urlObj.toString().replace(/\/$/, '') |
| 42 | |
| 43 | // Append path if provided |
| 44 | if (path) { |
| 45 | const cleanPath = path.startsWith('/') ? path : `/${path}` |
| 46 | return `${secureUrl}${cleanPath}` |
| 47 | } |
| 48 | |
| 49 | return secureUrl |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Constructs a secure link with a token parameter. |
no outgoing calls
no test coverage detected