Files
supabase/apps/studio/components/interfaces/Auth/Auth.constants.ts
Vaibhav 804475fd3a fix: redirect urls (#47487)
## TL;DR

fixes redirect url  normalization..

## PS:

| Before | After |
| --- | --- |
| Broken: whitespace could make the same redirect URL appear as a
separate entry and break delete behavior | Fixed: equivalent redirect
URLs are normalized consistently, so display, save, and delete behavior
stay in sync |
| <img width="800" height="274" alt="Before redirect URLs behavior"
src="https://github.com/user-attachments/assets/47dbb1ca-7c7d-482b-a67e-08c2eb2cd030"
/> | ![After redirect URLs
behavior](https://github.com/user-attachments/assets/b90dfad3-9ec2-4431-8412-34d4faca62da)
|

## ref:
- closes https://github.com/supabase/supabase/issues/47478

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved redirect URL handling so saved and displayed URLs are
consistently trimmed, normalized, deduplicated, and parsed from
comma-separated allow lists.
* Tightened redirect URL validation to better catch invalid formats and
prevent duplicates both against the existing allow list and within a new
submission.
* Fixed redirect URL deletion to remove the exact set of URLs confirmed
by the user.
* **Tests**
* Added/updated tests to cover redirect URL normalization and parsing
behavior for stored comma-separated allow lists.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-01 08:24:17 -06:00

64 lines
2.4 KiB
TypeScript

// Regex from: https://stackoverflow.com/a/68002755/4807782
// modified to accept numbers in the body of domain though
// examples of matches:
// "vercel.com"
// "www.vercel.com"
// "uptime-monitor-fe.vercel.app"
// "https://uptime-monitor-fe.vercel.app/"
// Supports wildcards, port numbers at the end, paths at the end and query params
const baseUrlRegex =
/^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_*-]+(\.[a-zA-Z0-9_*-]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/gm
// iOS deep linking scheme https://benoitpasquier.com/deep-linking-url-scheme-ios/
const appRegex =
/^[a-z0-9-]+([.][a-z0-9]+)*:\/(\/[-a-z0-9._~!$&'()*+,;=:@%]+)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/i
// Regex from https://stackoverflow.com/a/18696953/4807782
const localhostRegex = /^(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/i
// "chrome-extension://<extension-id>"
const chromeExtensionRegex = /chrome-extension:\/\/([a-zA-Z]*)/gm
// New regex for custom scheme URLs
const customSchemeRegex = /^([a-zA-Z][a-zA-Z0-9+.-]*):(?:\/{1,3})?([a-zA-Z0-9_.-]*)$/
// Exclude simple domain names without protocol
const excludeSimpleDomainRegex = /^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$/
// combine the above regexes, with optional exclusion of options
// usage: urlRegex() or urlRegex({ excludeSimpleDomains: false })
export function urlRegex(
options: { excludeSimpleDomains?: boolean } = { excludeSimpleDomains: true }
): RegExp {
const { excludeSimpleDomains } = options
const excludeSimpleDomainPart = excludeSimpleDomains
? `(?!${excludeSimpleDomainRegex.source})`
: ''
return new RegExp(
`${excludeSimpleDomainPart}((${baseUrlRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})|(${customSchemeRegex.source}))`,
'i'
)
}
export function normalizeRedirectUrl(url: string): string {
return url.trim().replace(/\s*,\s*$/, '')
}
export function parseRedirectUrls(allowList?: string | null): string[] {
if (!allowList) return []
return Array.from(
new Set(
allowList
.split(',')
.map(normalizeRedirectUrl)
.filter((url) => url.length > 0)
)
)
}
// Use a const string to represent no chars option. Represented as empty string on the backend side.
export const NO_REQUIRED_CHARACTERS = 'NO_REQUIRED_CHARS'