// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import Emm, {ManagedConfig} from '@mattermost/react-native-emm'; import {Alert, AlertButton, AppState, AppStateStatus, Platform} from 'react-native'; import JailMonkey from 'jail-monkey'; import {DEFAULT_LOCALE, getTranslations, t} from '@i18n'; import {getIOSAppGroupDetails} from '@utils/mattermost_managed'; const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000; class ManagedApp { backgroundSince = 0; enabled = false; inAppPinCode = false; performingAuthentication = false; previousAppState?: AppStateStatus; processConfigTimeout?: NodeJS.Timeout; vendor = 'Mattermost'; constructor() { Emm.addListener(this.processConfig); this.setIOSAppGroupIdentifier(); AppState.addEventListener('change', this.onAppStateChange); } init() { Emm.getManagedConfig().then(this.processConfig); } setIOSAppGroupIdentifier = () => { if (Platform.OS === 'ios') { const {appGroupIdentifier} = getIOSAppGroupDetails(); if (appGroupIdentifier) { Emm.setAppGroupId(appGroupIdentifier); } } } processConfig = async (config?: ManagedConfig) => { // If the managed configuration changed while authentication was // being performed, delay the processing of this new configuration // until authentication is complete. if (this.performingAuthentication) { if (this.processConfigTimeout) { clearTimeout(this.processConfigTimeout); } this.processConfigTimeout = setTimeout(() => this.processConfig(config), 500); } this.enabled = Boolean(config && Object.keys(config).length); if (!this.enabled) { return; } const blurScreen = config!.blurApplicationScreen === 'true'; Emm.enableBlurScreen(blurScreen); const vendor = config!.vendor; if (vendor) { this.vendor = vendor; } const jailbreakProtection = config!.jailbreakProtection === 'true'; if (jailbreakProtection && !this.isTrustedDevice()) { this.alertDeviceIsUntrusted(); return; } const inAppPinCode = config!.inAppPinCode === 'true'; if (inAppPinCode) { this.handleDeviceAuthentication(); } }; alertDeviceIsUntrusted = () => { const locale = DEFAULT_LOCALE; // TODO: Get current user or system locale const translations = getTranslations(locale); Alert.alert( translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor), translations[t('mobile.managed.jailbreak')].replace('{vendor}', this.vendor), [{ text: translations[t('mobile.managed.exit')], style: 'destructive', onPress: () => { Emm.exitApp(); }, }], {cancelable: false}, ); }; handleDeviceAuthentication = async (authExpired = true) => { this.performingAuthentication = true; const isSecured = await Emm.isDeviceSecured(); const locale = DEFAULT_LOCALE; const translations = getTranslations(locale); if (!isSecured) { await this.showNotSecuredAlert(translations); Emm.exitApp(); return; } if (authExpired) { try { await Emm.authenticate({ reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', this.vendor), fallback: true, supressEnterPassword: true, }); } catch (err) { Emm.exitApp(); return; } } this.performingAuthentication = false; }; isTrustedDevice = () => { return __DEV__ || JailMonkey.trustFall(); }; onAppStateChange = async (appState: AppStateStatus) => { const isActive = appState === 'active'; const isBackground = appState === 'background'; if (isActive && this.previousAppState === 'background') { if (this.enabled && this.inAppPinCode) { const authExpired = this.backgroundSince > 0 && (Date.now() - this.backgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER; await this.handleDeviceAuthentication(authExpired); } this.backgroundSince = 0; } else if (isBackground) { this.backgroundSince = Date.now(); } this.previousAppState = appState; }; showNotSecuredAlert = (translations: Record) => { return new Promise(async (resolve) => { /* eslint-disable-line no-async-promise-executor */ const buttons: AlertButton[] = []; if (Platform.OS === 'android') { buttons.push({ text: translations[t('mobile.managed.settings')], onPress: () => { Emm.openSecuritySettings(); }, }); } buttons.push({ text: translations[t('mobile.managed.exit')], onPress: resolve, style: 'cancel', }); let message; if (Platform.OS === 'ios') { const {face} = await Emm.deviceSecureWith(); if (face) { message = translations[t('mobile.managed.not_secured.ios')]; } else { message = translations[t('mobile.managed.not_secured.ios.touchId')]; } } else { message = translations[t('mobile.managed.not_secured.android')]; } Alert.alert( translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor), message, buttons, {cancelable: false, onDismiss: () => resolve}, ); }); }; } export default new ManagedApp();