11import { createLogger } from '@sim/logger'
2+ import { validateSalesforceInstanceUrl } from '@/lib/core/security/input-validation'
23import { isSalesforceLoginOrigin } from '@/lib/oauth/salesforce'
34
45const logger = createLogger ( 'SalesforceUtils' )
56
7+ /** The https origin of `url` when it is an org API host on a Salesforce domain. */
8+ function toInstanceOrigin ( url : string ) : string | undefined {
9+ const result = validateSalesforceInstanceUrl ( url )
10+ if ( ! result . isValid || ! result . sanitized || isSalesforceLoginOrigin ( result . sanitized ) ) {
11+ return undefined
12+ }
13+ return result . sanitized
14+ }
15+
616/**
717 * Extracts Salesforce instance URL from ID token or uses provided instance URL
818 * @param idToken - The Salesforce ID token containing instance URL
919 * @param instanceUrl - Direct instance URL if provided
10- * @returns The Salesforce instance URL
11- * @throws Error if instance URL cannot be determined
20+ * @returns The Salesforce instance URL, as an https origin on a Salesforce domain
21+ * @throws Error if instance URL cannot be determined or is not on a Salesforce domain
1222 */
1323export function getInstanceUrl ( idToken ?: string , instanceUrl ?: string ) : string {
14- if ( instanceUrl ) return instanceUrl
24+ if ( instanceUrl ) {
25+ const origin = toInstanceOrigin ( instanceUrl )
26+ if ( ! origin ) throw new Error ( 'Salesforce instance URL must be on a Salesforce domain' )
27+ return origin
28+ }
1529 if ( idToken ) {
1630 try {
1731 const base64Url = idToken . split ( '.' ) [ 1 ]
@@ -29,18 +43,8 @@ export function getInstanceUrl(idToken?: string, instanceUrl?: string): string {
2943 // `sub` be tried rather than short-circuiting the whole lookup.
3044 for ( const claim of [ decoded . profile , decoded . sub ] ) {
3145 if ( typeof claim !== 'string' ) continue
32- // `URL` rather than a hand-rolled prefix regex: it normalizes away
33- // userinfo, default ports, and case, so the origin compared against the
34- // login-host set is the same one a fetch would actually use.
35- let origin : string
36- try {
37- const url = new URL ( claim )
38- if ( url . protocol !== 'https:' ) continue
39- origin = url . origin
40- } catch {
41- continue
42- }
43- if ( ! isSalesforceLoginOrigin ( origin ) ) return origin
46+ const origin = toInstanceOrigin ( claim )
47+ if ( origin ) return origin
4448 }
4549 } catch ( error ) {
4650 logger . error ( 'Failed to decode Salesforce idToken' , { error } )
0 commit comments