-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
256 additions
and
301 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,18 @@ | ||
import { Appbar } from '#/Appbar/Appbar'; | ||
import { AuthSettings } from '#/auth/AuthSettings'; | ||
import { ScrollableScreenSurface } from '#/layout/ScrollableScreenSurface'; | ||
import { Scrollable } from '#/Scrollable'; | ||
import { ScreenSkeleton } from '#/skeleton/ScreenSkeleton'; | ||
import { withSuspense } from '#/skeleton/withSuspense'; | ||
import { createStyles } from '@theme/styles'; | ||
|
||
function AuthSettingsScreen() { | ||
return ( | ||
<> | ||
<Scrollable> | ||
<Appbar mode="large" leading="menu" headline="Authentication" /> | ||
|
||
<ScrollableScreenSurface contentContainerStyle={styles.surface}> | ||
<AuthSettings /> | ||
</ScrollableScreenSurface> | ||
</> | ||
<AuthSettings /> | ||
</Scrollable> | ||
); | ||
} | ||
|
||
const styles = createStyles({ | ||
surface: { | ||
flexGrow: 1, | ||
paddingTop: 8, | ||
}, | ||
}); | ||
|
||
export default withSuspense(AuthSettingsScreen, <ScreenSkeleton />); | ||
|
||
export { ErrorBoundary } from '#/ErrorBoundary'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,106 +1,106 @@ | ||
import { useImmerAtom } from 'jotai-immer'; | ||
import { Switch, Text } from 'react-native-paper'; | ||
import { Divider, Switch, Text } from 'react-native-paper'; | ||
import { ListItem } from '#/list/ListItem'; | ||
import { FingerprintIcon, LockOpenIcon, OutboundIcon } from '@theme/icons'; | ||
import { useEffect } from 'react'; | ||
import { useBiometrics } from '~/hooks/useBiometrics'; | ||
import { persistedAtom } from '~/lib/persistedAtom'; | ||
import { useAtomValue } from 'jotai'; | ||
import { SECURE_STORE_PASSWORD_ENCRYPTED as ALWAYS_REQUIRED_ON_OPEN } from '~/lib/secure-storage'; | ||
import { createStyles } from '@theme/styles'; | ||
import { ICON_SIZE } from '@theme/paper'; | ||
import { PasswordSettingsCard } from './PasswordSettingsCard'; | ||
import { PasswordSettings, usePasswordHash } from './PasswordSettings'; | ||
import { StyleProp, View, ViewStyle } from 'react-native'; | ||
import { Card } from '#/layout/Card'; | ||
import _ from 'lodash'; | ||
|
||
// Security note: this has weak security guarantees as an attacker with local access may change these settings, or even the whole JS bundle... | ||
const AUTH_SETTINGS = persistedAtom('AuthenticationSettings', { | ||
open: true, | ||
approval: true, | ||
}); | ||
export const useAuthRequiredOnOpen = () => | ||
useAtomValue(AUTH_SETTINGS).open || ALWAYS_REQUIRED_ON_OPEN; | ||
export const useAuthRequiredOnApproval = () => useAtomValue(AUTH_SETTINGS).approval; | ||
|
||
export interface AuthSettings2Props { | ||
style?: StyleProp<ViewStyle>; | ||
export function useAuthSettings() { | ||
const available = useAuthAvailable(); | ||
|
||
return _.mapValues(useAtomValue(AUTH_SETTINGS), (v) => v && available); | ||
} | ||
|
||
export function AuthSettings({ style }: AuthSettings2Props) { | ||
function useAuthAvailable() { | ||
const biometrics = useBiometrics(); | ||
const passwordHash = usePasswordHash(); | ||
|
||
const [settings, updateSettings] = useImmerAtom(AUTH_SETTINGS); | ||
|
||
// Enable biometrics (if supported) when this screen is first opened | ||
useEffect(() => { | ||
biometrics.setEnabled((enabled) => (enabled === null ? biometrics.available : enabled)); | ||
}, [biometrics]); | ||
|
||
return ( | ||
<View style={[styles.container, style]}> | ||
{biometrics.available && ( | ||
<Card type="outlined" style={[styles.indent, styles.biometrics]}> | ||
<FingerprintIcon size={ICON_SIZE.medium} /> | ||
return biometrics.available || !!passwordHash; | ||
} | ||
|
||
<Text variant="titleMedium" style={styles.cardHeadline}> | ||
Biometrics | ||
</Text> | ||
export interface AuthSettingsProps { | ||
style?: StyleProp<ViewStyle>; | ||
} | ||
|
||
<Switch value={!!biometrics.enabled} onValueChange={(v) => biometrics.setEnabled(v)} /> | ||
</Card> | ||
)} | ||
export function AuthSettings({ style }: AuthSettingsProps) { | ||
const biometrics = useBiometrics(); | ||
const available = useAuthAvailable(); | ||
const unlockDisabled = !available || ALWAYS_REQUIRED_ON_OPEN; | ||
|
||
<PasswordSettingsCard style={styles.indent} /> | ||
const settings = useAuthSettings(); | ||
const [, updateSettings] = useImmerAtom(AUTH_SETTINGS); | ||
|
||
<View> | ||
<Text variant="titleMedium" style={[styles.indent, styles.authOnHeader]}> | ||
Authenticate on | ||
</Text> | ||
return ( | ||
<View style={style}> | ||
<PasswordSettings style={styles.indent} /> | ||
<Divider style={[styles.divider, styles.indent]} /> | ||
|
||
{biometrics.available && ( | ||
<ListItem | ||
leading={LockOpenIcon} | ||
headline="Unlock app" | ||
trailing={() => ( | ||
<Switch | ||
value={!ALWAYS_REQUIRED_ON_OPEN && settings.open} | ||
onValueChange={(v) => updateSettings((s) => ({ ...s, open: v }))} | ||
/> | ||
)} | ||
disabled={ALWAYS_REQUIRED_ON_OPEN} | ||
leading={FingerprintIcon} | ||
headline="Biometrics" | ||
supporting="Allow biometrics to be used" | ||
trailing={ | ||
<Switch value={biometrics.enabled} onValueChange={(v) => biometrics.setEnabled(v)} /> | ||
} | ||
/> | ||
)} | ||
|
||
<ListItem | ||
leading={OutboundIcon} | ||
headline="Approve action" | ||
trailing={() => ( | ||
<Switch | ||
value={settings.approval} | ||
onValueChange={(v) => updateSettings((s) => ({ ...s, approval: v }))} | ||
/> | ||
)} | ||
/> | ||
</View> | ||
<Text variant="labelLarge" style={[styles.indent, styles.label]}> | ||
Required to | ||
</Text> | ||
|
||
<ListItem | ||
leading={LockOpenIcon} | ||
headline="Unlock app" | ||
trailing={() => ( | ||
<Switch | ||
value={settings.open} | ||
onValueChange={(v) => updateSettings((s) => ({ ...s, open: v }))} | ||
disabled={unlockDisabled} | ||
/> | ||
)} | ||
disabled={unlockDisabled} | ||
/> | ||
|
||
<ListItem | ||
leading={OutboundIcon} | ||
headline="Approve action" | ||
trailing={() => ( | ||
<Switch | ||
value={settings.approval} | ||
onValueChange={(v) => updateSettings((s) => ({ ...s, approval: v }))} | ||
disabled={!available} | ||
/> | ||
)} | ||
disabled={!available} | ||
/> | ||
</View> | ||
); | ||
} | ||
|
||
const styles = createStyles({ | ||
container: { | ||
gap: 8, | ||
divider: { | ||
marginTop: 16, | ||
marginBottom: 8, | ||
}, | ||
indent: { | ||
marginHorizontal: 16, | ||
}, | ||
biometrics: { | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: 16, | ||
padding: 16, | ||
}, | ||
cardHeadline: { | ||
flex: 1, | ||
}, | ||
authOnHeader: { | ||
marginVertical: 8, | ||
label: { | ||
marginTop: 16, | ||
marginBottom: 8, | ||
}, | ||
}); |
Oops, something went wrong.