[Gekidou] Show login screen when selecting a previous server (#6190)

* Show login screen when selecting a previous server

* Update app/screens/home/channel_list/servers/servers_list/server_item/server_item.tsx

Co-authored-by: Jason Frerich <jason.frerich@mattermost.com>

Co-authored-by: Jason Frerich <jason.frerich@mattermost.com>
This commit is contained in:
Elias Nahum
2022-04-25 13:26:31 -04:00
committed by GitHub
parent 1a4040252a
commit bb3a3112b1
8 changed files with 144 additions and 32 deletions

View File

@@ -9,7 +9,9 @@ import Swipeable from 'react-native-gesture-handler/Swipeable';
import {storeMultiServerTutorial} from '@actions/app/global';
import {appEntry} from '@actions/remote/entry';
import {doPing} from '@actions/remote/general';
import {logout} from '@actions/remote/session';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import CompassIcon from '@components/compass_icon';
import Loading from '@components/loading';
import ServerIcon from '@components/server_icon';
@@ -21,7 +23,7 @@ import DatabaseManager from '@database/manager';
import {subscribeServerUnreadAndMentions} from '@database/subscription/unreads';
import {useIsTablet} from '@hooks/device';
import {dismissBottomSheet} from '@screens/navigation';
import {addNewServer, alertServerLogout, alertServerRemove, editServer} from '@utils/server';
import {alertServerError, alertServerLogout, alertServerRemove, editServer, loginToServer} from '@utils/server';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {removeProtocol, stripTrailingSlashes} from '@utils/url';
@@ -195,8 +197,22 @@ const ServerItem = ({highlight, isActive, server, tutorialWatched}: Props) => {
return style;
}, [server.lastActiveAt]);
const handleLogin = useCallback(() => {
addNewServer(theme, server.url, displayName);
const handleLogin = useCallback(async () => {
swipeable.current?.close();
setSwitching(true);
const result = await doPing(server.url);
if (result.error) {
alertServerError(intl, result.error as ClientErrorProps);
setSwitching(false);
return;
}
const data = await fetchConfigAndLicense(server.url, true);
if (data.error) {
alertServerError(intl, data.error as ClientErrorProps);
setSwitching(false);
return;
}
loginToServer(theme, server.url, displayName, data.config!, data.license!);
}, [server, theme, intl]);
const handleDismissTutorial = useCallback(() => {

View File

@@ -11,8 +11,9 @@ import {SafeAreaView} from 'react-native-safe-area-context';
import FormattedText from '@components/formatted_text';
import {Screens} from '@constants';
import {useIsTablet} from '@hooks/device';
import NetworkManager from '@managers/network_manager';
import Background from '@screens/background';
import {goToScreen, loginAnimationOptions} from '@screens/navigation';
import {dismissModal, goToScreen, loginAnimationOptions} from '@screens/navigation';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
@@ -24,6 +25,8 @@ import SsoOptions from './sso_options';
import type {LaunchProps} from '@typings/launch';
export interface LoginOptionsProps extends LaunchProps {
closeButtonId?: string;
componentId: string;
config: ClientConfig;
hasLoginForm: boolean;
license: ClientLicense;
@@ -68,7 +71,11 @@ const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
const LoginOptions = ({config, extra, hasLoginForm, launchType, launchError, license, serverDisplayName, serverUrl, ssoOptions, theme}: LoginOptionsProps) => {
const LoginOptions = ({
closeButtonId, componentId, config, extra,
hasLoginForm, launchType, launchError, license,
serverDisplayName, serverUrl, ssoOptions, theme,
}: LoginOptionsProps) => {
const styles = getStyles(theme);
const keyboardAwareRef = useRef<KeyboardAwareScrollView>();
const dimensions = useWindowDimensions();
@@ -125,6 +132,17 @@ const LoginOptions = ({config, extra, hasLoginForm, launchType, launchError, lic
};
}, []);
useEffect(() => {
const navigationEvents = Navigation.events().registerNavigationButtonPressedListener(({buttonId}) => {
if (closeButtonId && buttonId === closeButtonId) {
NetworkManager.invalidateClient(serverUrl);
dismissModal({componentId});
}
});
return () => navigationEvents.remove();
}, []);
useEffect(() => {
const listener = {
componentDidAppear: () => {

View File

@@ -184,6 +184,8 @@ export function resetToHome(passProps: LaunchProps = {launchType: LaunchType.Nor
if (passProps.launchType === LaunchType.AddServer) {
dismissModal({componentId: Screens.SERVER});
dismissModal({componentId: Screens.LOGIN});
dismissModal({componentId: Screens.SSO});
dismissModal({componentId: Screens.BOTTOM_SHEET});
return;
}

View File

@@ -15,7 +15,7 @@ import {fetchConfigAndLicense} from '@actions/remote/systems';
import LocalConfig from '@assets/config.json';
import ClientError from '@client/rest/error';
import AppVersion from '@components/app_version';
import {Screens, Sso} from '@constants';
import {Screens} from '@constants';
import DatabaseManager from '@database/manager';
import {t} from '@i18n';
import NetworkManager from '@managers/network_manager';
@@ -24,6 +24,7 @@ import Background from '@screens/background';
import {dismissModal, goToScreen, loginAnimationOptions} from '@screens/navigation';
import {DeepLinkWithData, LaunchProps, LaunchType} from '@typings/launch';
import {getErrorMessage} from '@utils/client_error';
import {loginOptions} from '@utils/server';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {getServerUrlAfterRedirect, isValidUrl, sanitizeUrl} from '@utils/url';
@@ -136,6 +137,7 @@ const Server = ({
useEffect(() => {
const navigationEvents = Navigation.events().registerNavigationButtonPressedListener(({buttonId}) => {
if (closeButtonId && buttonId === closeButtonId) {
NetworkManager.invalidateClient(url);
dismissModal({componentId});
}
});
@@ -144,24 +146,7 @@ const Server = ({
}, []);
const displayLogin = (serverUrl: string, config: ClientConfig, license: ClientLicense) => {
const isLicensed = license.IsLicensed === 'true';
const samlEnabled = config.EnableSaml === 'true' && isLicensed && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
const googleEnabled = config.EnableSignUpWithGoogle === 'true' && isLicensed;
const o365Enabled = config.EnableSignUpWithOffice365 === 'true' && isLicensed && license.Office365OAuth === 'true';
const openIdEnabled = config.EnableSignUpWithOpenId === 'true' && isLicensed;
const ldapEnabled = isLicensed && config.EnableLdap === 'true' && license.LDAP === 'true';
const hasLoginForm = config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true' || ldapEnabled;
const ssoOptions: Record<string, boolean> = {
[Sso.SAML]: samlEnabled,
[Sso.GITLAB]: gitlabEnabled,
[Sso.GOOGLE]: googleEnabled,
[Sso.OFFICE365]: o365Enabled,
[Sso.OPENID]: openIdEnabled,
};
const enabledSSOs = Object.keys(ssoOptions).filter((key) => ssoOptions[key]);
const numberSSOs = enabledSSOs.length;
const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license);
const passProps = {
config,
extra,

View File

@@ -11,8 +11,9 @@ import {SafeAreaView} from 'react-native-safe-area-context';
import {ssoLogin} from '@actions/remote/session';
import ClientError from '@client/rest/error';
import {Screens, Sso} from '@constants';
import NetworkManager from '@managers/network_manager';
import Background from '@screens/background';
import {resetToHome} from '@screens/navigation';
import {dismissModal, resetToHome} from '@screens/navigation';
import SSOWithRedirectURL from './sso_with_redirect_url';
import SSOWithWebView from './sso_with_webview';
@@ -20,11 +21,13 @@ import SSOWithWebView from './sso_with_webview';
import type {LaunchProps} from '@typings/launch';
interface SSOProps extends LaunchProps {
config: Partial<ClientConfig>;
license: Partial<ClientLicense>;
ssoType: string;
serverDisplayName: string;
theme: Theme;
closeButtonId?: string;
componentId: string;
config: Partial<ClientConfig>;
license: Partial<ClientLicense>;
ssoType: string;
serverDisplayName: string;
theme: Theme;
}
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
@@ -35,7 +38,11 @@ const styles = StyleSheet.create({
},
});
const SSO = ({config, extra, launchError, launchType, serverDisplayName, serverUrl, ssoType, theme}: SSOProps) => {
const SSO = ({
closeButtonId, componentId, config, extra,
launchError, launchType, serverDisplayName,
serverUrl, ssoType, theme,
}: SSOProps) => {
const managedConfig = useManagedConfig<ManagedConfig>();
const inAppSessionAuth = managedConfig?.inAppSessionAuth === 'true';
const dimensions = useWindowDimensions();
@@ -128,6 +135,19 @@ const SSO = ({config, extra, launchError, launchType, serverDisplayName, serverU
return () => unsubscribe.remove();
}, [dimensions]);
useEffect(() => {
const navigationEvents = Navigation.events().registerNavigationButtonPressedListener(({buttonId}) => {
if (closeButtonId && buttonId === closeButtonId) {
if (serverUrl) {
NetworkManager.invalidateClient(serverUrl);
}
dismissModal({componentId});
}
});
return () => navigationEvents.remove();
}, []);
const props = {
doSSOLogin,
loginError,

View File

@@ -23,6 +23,7 @@ jest.mock('@utils/url', () => {
describe('SSO', () => {
const baseProps = {
componentId: 'SSO',
license: {
IsLicensed: 'true',
},

View File

@@ -5,9 +5,10 @@ import {IntlShape} from 'react-intl';
import {Alert, AlertButton} from 'react-native';
import CompassIcon from '@components/compass_icon';
import {Screens, SupportedServer} from '@constants';
import {Screens, Sso, SupportedServer} from '@constants';
import {dismissBottomSheet, showModal} from '@screens/navigation';
import {LaunchType} from '@typings/launch';
import {getErrorMessage} from '@utils/client_error';
import {changeOpacity} from '@utils/theme';
import {tryOpenURL} from '@utils/url';
@@ -49,6 +50,61 @@ export async function addNewServer(theme: Theme, serverUrl?: string, displayName
showModal(Screens.SERVER, '', props, options);
}
export function loginOptions(config: ClientConfig, license: ClientLicense) {
const isLicensed = license.IsLicensed === 'true';
const samlEnabled = config.EnableSaml === 'true' && isLicensed && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
const googleEnabled = config.EnableSignUpWithGoogle === 'true' && isLicensed;
const o365Enabled = config.EnableSignUpWithOffice365 === 'true' && isLicensed && license.Office365OAuth === 'true';
const openIdEnabled = config.EnableSignUpWithOpenId === 'true' && isLicensed;
const ldapEnabled = isLicensed && config.EnableLdap === 'true' && license.LDAP === 'true';
const hasLoginForm = config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true' || ldapEnabled;
const ssoOptions: Record<string, boolean> = {
[Sso.SAML]: samlEnabled,
[Sso.GITLAB]: gitlabEnabled,
[Sso.GOOGLE]: googleEnabled,
[Sso.OFFICE365]: o365Enabled,
[Sso.OPENID]: openIdEnabled,
};
const enabledSSOs = Object.keys(ssoOptions).filter((key) => ssoOptions[key]);
const numberSSOs = enabledSSOs.length;
return {
enabledSSOs,
hasLoginForm,
numberSSOs,
ssoOptions,
};
}
export async function loginToServer(theme: Theme, serverUrl: string, displayName: string, config: ClientConfig, license: ClientLicense) {
await dismissBottomSheet();
const closeButtonId = 'close-server';
const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license);
const props = {
closeButtonId,
config,
hasLoginForm,
launchType: LaunchType.AddServer,
license,
serverDisplayName: displayName,
serverUrl,
ssoOptions,
theme,
};
const redirectSSO = !hasLoginForm && numberSSOs === 1;
const screen = redirectSSO ? Screens.SSO : Screens.LOGIN;
if (redirectSSO) {
// @ts-expect-error ssoType not in definition
props.ssoType = enabledSSOs[0];
}
const options = buildServerModalOptions(theme, closeButtonId);
showModal(screen, '', props, options);
}
export async function editServer(theme: Theme, server: ServersModel) {
const closeButtonId = 'close-server-edit';
const props = {
@@ -103,6 +159,17 @@ export async function alertServerRemove(displayName: string, onPress: () => void
);
}
export function alertServerError(intl: IntlShape, error: ClientErrorProps) {
const message = getErrorMessage(error, intl);
Alert.alert(
intl.formatMessage({
id: 'server.websocket.unreachable',
defaultMessage: 'Server is unreachable.',
}),
message,
);
}
function unsupportedServerAdminAlert(intl: IntlShape) {
const title = intl.formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'});

View File

@@ -270,6 +270,9 @@ jest.mock('react-native-navigation', () => {
bindComponent: jest.fn(() => {
return {remove: jest.fn()};
}),
registerNavigationButtonPressedListener: jest.fn(() => {
return {buttonId: 'buttonId'};
}),
}),
setRoot: jest.fn(),
pop: jest.fn(),