@@ -11,6 +11,7 @@ import urlParse from 'url-parse';
1111import { Sso } from '@constants' ;
1212import { isBetaApp } from '@utils/general' ;
1313import { createSamlChallenge } from '@utils/saml_challenge' ;
14+ import { sanitizeUrl } from '@utils/url' ;
1415
1516import AuthError from './components/auth_error' ;
1617import AuthRedirect from './components/auth_redirect' ;
@@ -21,6 +22,7 @@ interface SSOAuthenticationProps {
2122 doSSOCodeExchange : ( loginCode : string , samlChallenge : { codeVerifier : string ; state : string } ) => void ;
2223 loginError : string ;
2324 loginUrl : string ;
25+ serverUrl : string ;
2426 setLoginError : ( value : string ) => void ;
2527 theme : Theme ;
2628}
@@ -32,7 +34,7 @@ const style = StyleSheet.create({
3234 } ,
3335} ) ;
3436
35- const SSOAuthentication = ( { doSSOLogin, doSSOCodeExchange, loginError, loginUrl, setLoginError, theme} : SSOAuthenticationProps ) => {
37+ const SSOAuthentication = ( { doSSOLogin, doSSOCodeExchange, loginError, loginUrl, serverUrl , setLoginError, theme} : SSOAuthenticationProps ) => {
3638 const [ error , setError ] = useState < string > ( '' ) ;
3739 const [ loginSuccess , setLoginSuccess ] = useState ( false ) ;
3840 const intl = useIntl ( ) ;
@@ -43,6 +45,17 @@ const SSOAuthentication = ({doSSOLogin, doSSOCodeExchange, loginError, loginUrl,
4345
4446 const redirectUrl = customUrlScheme + 'callback' ;
4547 const samlChallenge = useMemo ( ( ) => createSamlChallenge ( ) , [ ] ) ;
48+
49+ // Verify that the srv parameter from the callback matches the expected server
50+ const verifyServerOrigin = useCallback ( ( srvParam : string | undefined ) : boolean => {
51+ if ( ! srvParam ) {
52+ // Old servers don't send srv parameter - allow for backwards compatibility
53+ return true ;
54+ }
55+ const normalizedExpected = sanitizeUrl ( serverUrl ) ;
56+ const normalizedActual = sanitizeUrl ( srvParam ) ;
57+ return normalizedExpected === normalizedActual ;
58+ } , [ serverUrl ] ) ;
4659 const init = useCallback ( async ( resetErrors = true ) => {
4760 setLoginSuccess ( false ) ;
4861 if ( resetErrors !== false ) {
@@ -62,6 +75,19 @@ const SSOAuthentication = ({doSSOLogin, doSSOCodeExchange, loginError, loginUrl,
6275 const result = await openAuthSessionAsync ( url , null , { preferEphemeralSession : true , createTask : false } ) ;
6376 if ( 'url' in result && result . url ) {
6477 const resultUrl = urlParse ( result . url , true ) ;
78+ const srvParam = resultUrl . query ?. srv as string | undefined ;
79+
80+ // Verify server origin before accepting credentials
81+ if ( ! verifyServerOrigin ( srvParam ) ) {
82+ setError (
83+ intl . formatMessage ( {
84+ id : 'mobile.oauth.server_mismatch' ,
85+ defaultMessage : 'Login failed: Unable to complete authentication with this server. Please try again.' ,
86+ } ) ,
87+ ) ;
88+ return ;
89+ }
90+
6591 const loginCode = resultUrl . query ?. login_code as string | undefined ;
6692 if ( loginCode ) {
6793 // Prefer code exchange when available
@@ -83,7 +109,7 @@ const SSOAuthentication = ({doSSOLogin, doSSOCodeExchange, loginError, loginUrl,
83109 } ) ,
84110 ) ;
85111 }
86- } , [ doSSOCodeExchange , doSSOLogin , intl , loginUrl , samlChallenge , redirectUrl , setLoginError ] ) ;
112+ } , [ doSSOCodeExchange , doSSOLogin , intl , loginUrl , samlChallenge , redirectUrl , setLoginError , verifyServerOrigin ] ) ;
87113
88114 useEffect ( ( ) => {
89115 let listener : EventSubscription | null = null ;
@@ -93,6 +119,19 @@ const SSOAuthentication = ({doSSOLogin, doSSOCodeExchange, loginError, loginUrl,
93119 setError ( '' ) ;
94120 if ( url && url . startsWith ( redirectUrl ) ) {
95121 const parsedUrl = urlParse ( url , true ) ;
122+ const srvParam = parsedUrl . query ?. srv as string | undefined ;
123+
124+ // Verify server origin before accepting credentials
125+ if ( ! verifyServerOrigin ( srvParam ) ) {
126+ setError (
127+ intl . formatMessage ( {
128+ id : 'mobile.oauth.server_mismatch' ,
129+ defaultMessage : 'Login failed: Unable to complete authentication with this server. Please try again.' ,
130+ } ) ,
131+ ) ;
132+ return ;
133+ }
134+
96135 const loginCode = parsedUrl . query ?. login_code as string | undefined ;
97136 if ( loginCode ) {
98137 setLoginSuccess ( true ) ;
@@ -126,7 +165,7 @@ const SSOAuthentication = ({doSSOLogin, doSSOCodeExchange, loginError, loginUrl,
126165 clearTimeout ( timeout ) ;
127166 listener ?. remove ( ) ;
128167 } ;
129- } , [ doSSOCodeExchange , doSSOLogin , init , intl , samlChallenge , redirectUrl ] ) ;
168+ } , [ doSSOCodeExchange , doSSOLogin , init , intl , samlChallenge , redirectUrl , verifyServerOrigin ] ) ;
130169
131170 let content ;
132171 if ( loginSuccess ) {
0 commit comments