Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

open an external browser when the server config for MobileExternalBrowse is set to true #8220

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions app/screens/sso/components/auth_error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {Button} from '@rneui/base';
import React from 'react';
import {Text, View} from 'react-native';

import FormattedText from '@components/formatted_text';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';

interface AuthErrorProps {
error: string;
retry: () => void;
theme: Theme;
}

const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
button: {
marginTop: 25,
},
errorText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
textAlign: 'center',
...typography('Body', 200, 'Regular'),
},
infoContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
infoText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
...typography('Body', 100, 'Regular'),
},
infoTitle: {
color: theme.centerChannelColor,
marginBottom: 4,
...typography('Heading', 700),
},
};
});

const AuthError = ({error, retry, theme}: AuthErrorProps) => {
const style = getStyleSheet(theme);

return (
<View style={style.infoContainer}>
<FormattedText
id='mobile.oauth.switch_to_browser.error_title'
testID='mobile.oauth.switch_to_browser.error_title'
defaultMessage='Sign in error'
style={style.infoTitle}
/>
<Text style={style.errorText}>
{`${error}.`}
</Text>
<Button
buttonStyle={[style.button, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]}
testID='mobile.oauth.try_again'
onPress={retry}
>
<FormattedText
id='mobile.oauth.try_again'
defaultMessage='Try again'
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
/>
</Button>
larkox marked this conversation as resolved.
Show resolved Hide resolved
</View>
);
};

export default AuthError;
55 changes: 55 additions & 0 deletions app/screens/sso/components/auth_redirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {View} from 'react-native';

import FormattedText from '@components/formatted_text';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';

interface AuthRedirectProps {
theme: Theme;
}

const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
infoContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
infoText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
...typography('Body', 100, 'Regular'),
},
infoTitle: {
color: theme.centerChannelColor,
marginBottom: 4,
...typography('Heading', 700),
},
};
});

const AuthRedirect = ({theme}: AuthRedirectProps) => {
const style = getStyleSheet(theme);

return (
<View style={style.infoContainer}>
<FormattedText
id='mobile.oauth.switch_to_browser.title'
testID='mobile.oauth.switch_to_browser.title'
defaultMessage='Redirecting...'
style={style.infoTitle}
/>
<FormattedText
id='mobile.oauth.switch_to_browser'
testID='mobile.oauth.switch_to_browser'
defaultMessage='You are being redirected to your login provider'
style={style.infoText}
/>
</View>
);
};

export default AuthRedirect;
57 changes: 57 additions & 0 deletions app/screens/sso/components/auth_success.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {View} from 'react-native';

import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';

interface AuthSuccessProps {
theme: Theme;
}

const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
infoContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
infoText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
...typography('Body', 100, 'Regular'),
},
infoTitle: {
color: theme.centerChannelColor,
marginBottom: 4,
...typography('Heading', 700),
},
};
});

const AuthSuccess = ({theme}: AuthSuccessProps) => {
const style = getStyleSheet(theme);

return (
<View style={style.infoContainer}>
<Loading/>
<FormattedText
id='mobile.oauth.success.title'
testID='mobile.oauth.success.title'
defaultMessage='Authentication successful'
style={style.infoTitle}
/>
<FormattedText
id='mobile.oauth.success.description'
testID='mobile.oauth.success.description'
defaultMessage='Signing in now, just a moment...'
style={style.infoText}
/>
</View>
);
};

export default AuthSuccess;
23 changes: 19 additions & 4 deletions app/screens/sso/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {getFullErrorMessage, isErrorWithUrl} from '@utils/errors';
import {logWarning} from '@utils/log';

import SSOAuthentication from './sso_authentication';
import SSOAuthenticationWithExternalBrowser from './sso_authentication_with_external_browser';

import type {LaunchProps} from '@typings/launch';
import type {AvailableScreens} from '@typings/screens/navigation';
Expand Down Expand Up @@ -155,14 +156,28 @@ const SSO = ({
theme,
};

let authentication;
if (config.MobileExternalBrowser === 'true') {
Copy link
Contributor

@rahimrahman rahimrahman Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enahum any chance we can or should make it easier to pass an env so that we can do any type of test without having to update the server?

I was thinking about E2E test, or for QA to test without having to modify the config if we need to test this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be done by the time we introduce e2e for this. That e2e will require the server to be configured with a SSO anyway

authentication = (
<SSOAuthenticationWithExternalBrowser
{...props}
serverUrl={serverUrl!}
/>
);
} else {
authentication = (
<SSOAuthentication
{...props}
serverUrl={serverUrl!}
/>
);
}

return (
<View style={styles.flex}>
<Background theme={theme}/>
<AnimatedSafeArea style={[styles.flex, transform]}>
<SSOAuthentication
{...props}
serverUrl={serverUrl!}
/>
{authentication}
</AnimatedSafeArea>
</View>
);
Expand Down
115 changes: 20 additions & 95 deletions app/screens/sso/sso_authentication.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {Button} from '@rneui/base';
import {openAuthSessionAsync} from 'expo-web-browser';
import qs from 'querystringify';
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {Linking, Platform, Text, View, type EventSubscription} from 'react-native';
import {Linking, Platform, StyleSheet, View, type EventSubscription} from 'react-native';
import urlParse from 'url-parse';

import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import {Sso} from '@constants';
import NetworkManager from '@managers/network_manager';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {isBetaApp} from '@utils/general';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';

interface SSOWithRedirectURLProps {
import AuthError from './components/auth_error';
import AuthRedirect from './components/auth_redirect';
import AuthSuccess from './components/auth_success';

interface SSOAuthenticationProps {
doSSOLogin: (bearerToken: string, csrfToken: string) => void;
loginError: string;
loginUrl: string;
Expand All @@ -27,41 +25,16 @@ interface SSOWithRedirectURLProps {
theme: Theme;
}

const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
button: {
marginTop: 25,
},
container: {
flex: 1,
paddingHorizontal: 24,
},
errorText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
textAlign: 'center',
...typography('Body', 200, 'Regular'),
},
infoContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
infoText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
...typography('Body', 100, 'Regular'),
},
infoTitle: {
color: theme.centerChannelColor,
marginBottom: 4,
...typography('Heading', 700),
},
};
const style = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 24,
},
});

const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLoginError, theme}: SSOWithRedirectURLProps) => {
const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLoginError, theme}: SSOAuthenticationProps) => {
const [error, setError] = useState<string>('');
const [loginSuccess, setLoginSuccess] = useState(false);
const style = getStyleSheet(theme);
const intl = useIntl();
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
if (isBetaApp) {
Expand All @@ -84,7 +57,7 @@ const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLogi
};
parsedUrl.set('query', qs.stringify(query));
const url = parsedUrl.toString();
const result = await openAuthSessionAsync(url, null, {preferEphemeralSession: true});
const result = await openAuthSessionAsync(url, null, {preferEphemeralSession: true, createTask: false});
if ('url' in result && result.url) {
const resultUrl = urlParse(result.url, true);
const bearerToken = resultUrl.query?.MMAUTHTOKEN;
Expand Down Expand Up @@ -142,65 +115,17 @@ const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLogi

let content;
if (loginSuccess) {
content = (
<View style={style.infoContainer}>
<Loading/>
<FormattedText
id='mobile.oauth.success.title'
testID='mobile.oauth.success.title'
defaultMessage='Authentication successful'
style={style.infoTitle}
/>
<FormattedText
id='mobile.oauth.success.description'
testID='mobile.oauth.success.description'
defaultMessage='Signing in now, just a moment...'
style={style.infoText}
/>
</View>
);
content = (<AuthSuccess theme={theme}/>);
} else if (loginError || error) {
content = (
<View style={style.infoContainer}>
<FormattedText
id='mobile.oauth.switch_to_browser.error_title'
testID='mobile.oauth.switch_to_browser.error_title'
defaultMessage='Sign in error'
style={style.infoTitle}
/>
<Text style={style.errorText}>
{`${loginError || error}.`}
</Text>
<Button
buttonStyle={[style.button, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]}
testID='mobile.oauth.try_again'
onPress={() => init()}
>
<FormattedText
id='mobile.oauth.try_again'
defaultMessage='Try again'
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
/>
</Button>
</View>
<AuthError
error={loginError || error}
retry={init}
theme={theme}
/>
);
} else {
content = (
<View style={style.infoContainer}>
<FormattedText
id='mobile.oauth.switch_to_browser.title'
testID='mobile.oauth.switch_to_browser.title'
defaultMessage='Redirecting...'
style={style.infoTitle}
/>
<FormattedText
id='mobile.oauth.switch_to_browser'
testID='mobile.oauth.switch_to_browser'
defaultMessage='You are being redirected to your login provider'
style={style.infoText}
/>
</View>
);
content = (<AuthRedirect theme={theme}/>);
}

return (
Expand Down
Loading
Loading