mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-05 02:18:56 +00:00
* On iOS, resuming after the 5-minute window showed no biometric prompt — just a blocking alert whose only button logged the user out. A failure to evaluate device security was indistinguishable from a verdict that the device is not secured. Android had the same class of bug, where user-cancel codes led to logout. Outcomes are now classified three ways, so only a genuine credential rejection or a genuinely unsecured device acts destructively; cancels and indeterminate results offer a retry. SecurityManager and ManagedApp share the policy-free parts via app/utils/security/. Also fixes a resume stacking a second prompt behind an open alert, a backgroundSince bypass, and a latched flag that disabled the MDM PIN. * feedback review * gate app state * fix tests
44 lines
1.9 KiB
TypeScript
44 lines
1.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import Emm, {AuthenticationOutcome} from '@mattermost/react-native-emm';
|
|
|
|
import {getFullErrorMessage} from '@utils/errors';
|
|
import {logDebug} from '@utils/log';
|
|
|
|
import {DEVICE_SECURED_RETRY_DELAY, type AuthenticationSource} from './constants';
|
|
|
|
export type DeviceSecuredResult = 'secured' | 'notSecured' | 'interrupted';
|
|
|
|
// Not an instanceof check: the error may cross a module boundary. Anything unrecognised is
|
|
// indeterminate, so an unexpected failure never destroys a session or closes the app.
|
|
export const getAuthenticationOutcome = (error: unknown): AuthenticationOutcome => {
|
|
const outcome = (error as {outcome?: AuthenticationOutcome} | undefined)?.outcome;
|
|
return outcome ?? AuthenticationOutcome.Indeterminate;
|
|
};
|
|
|
|
/**
|
|
* Checks whether the device is secured, retrying once when the check cannot be performed.
|
|
*
|
|
* `interrupted` is not a verdict about the device: reporting it as unsecured would send the
|
|
* caller down a destructive path. The probe is silent, so retrying to absorb the iOS
|
|
* app-switcher animation costs the user nothing.
|
|
*/
|
|
export const probeDeviceSecured = async (source: AuthenticationSource): Promise<DeviceSecuredResult> => {
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
try {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const secured = await Emm.isDeviceSecured();
|
|
return secured ? 'secured' : 'notSecured';
|
|
} catch (error) {
|
|
logDebug(`${source}: Could not determine device security`, {attempt, reason: getFullErrorMessage(error)});
|
|
if (attempt === 0) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await new Promise((resolve) => setTimeout(resolve, DEVICE_SECURED_RETRY_DELAY));
|
|
}
|
|
}
|
|
}
|
|
|
|
return 'interrupted';
|
|
};
|