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

Jon/ui4/login-request #224

Merged
merged 12 commits into from
Nov 12, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- added: (Android) Detect if user restricted background battery usage
- changed: `SecurityAlertsScene` and `SecurityAlertsModal` redesign

## 3.23.0 (2024-11-01)

Expand Down
14 changes: 12 additions & 2 deletions src/common/locales/strings/enUS.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"account_confirmation": "Account Confirmation",
"account_locked_for": "Account locked for \n%1$s more seconds",
"access_confirmation_title": "Access Confirmation",
"access_confirmation_body": "I understand that granting access to this login attempt gives this person full access to my funds and that this action is not reversible.",
"alert_dropdown_alert": "Alert! ",
"alert_dropdown_warning": "Warning! ",
"alert_modal_action": "\nPlease log in to approve or deny this alert",
Expand All @@ -15,7 +17,11 @@
"alert_scene_message": "A new device would like to log in to your account.",
"alert_scene_reset_date": "Unless denied, access will be granted on: ",
"alert_scene_reset_message": "Someone is trying to remove 2-factor security from your account.",
"alert_scene_warning": "If you did not request this login, your password may have been stolen and you could lose funds. Please deny the request and change your password.",
"alert_scene_message_many_new": "Several new devices would like to log in to your account.",
"alert_scene_warning_review": "If you did not request this login, your password may have been stolen and you could lose funds. Please review this request.",
"allow": "Allow",
"deny": "Deny",
"deny_all": "Deny All",
"answer_case_sensitive": "Answers are case sensitive",
"answers_four_chanracters": "Answers should be minimum of 4 characters",
"app_logo_hint": "App logo",
Expand Down Expand Up @@ -73,6 +79,7 @@
"great_job": "Great Job!",
"hang_tight": "Hang tight while we create",
"hide_account_info": "Hide account information",
"i_understand_agree": "I understand and agree",
"initiate_password_recovery": "Enter Recovery Token. You can find the recovery token in an email that was sent from yourself if password recovery was setup prior to losing access.",
"invalid_account": "Account does not exist",
"invalid_credentials": "Invalid username or password",
Expand Down Expand Up @@ -228,5 +235,8 @@
"welcome": "Welcome to %s!",
"your_answer_label": "Your Answer",
"complete_captcha_title": "Are you a human?",
"failed_captcha_error": "Incorrect solution"
"failed_captcha_error": "Incorrect solution",
"review_login_request": "Review Login Request",
"login_attempt_detected": "A login attempt has been detected for the following account(s):",
"twofa_attempt_detected": "An attempt to remove 2-factor security has been detected for the following account(s):"
}
7 changes: 3 additions & 4 deletions src/components/common/SceneButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@

import * as React from 'react'

import { ButtonsViewUi4, ButtonsViewUi4Props } from '../ui4/ButtonsViewUi4'
import { ButtonsView, ButtonsViewProps } from '../ui4/ButtonsView'

interface Props
extends Omit<Omit<ButtonsViewUi4Props, 'parentType'>, 'layout'> {}
interface Props extends Omit<Omit<ButtonsViewProps, 'parentType'>, 'layout'> {}

export const SceneButtons = (props: Props) => {
return <ButtonsViewUi4 {...props} parentType="scene" />
return <ButtonsView {...props} parentType="scene" />
}
6 changes: 3 additions & 3 deletions src/components/modals/ButtonsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { showError } from '../services/AirshipInstance'
import { useTheme } from '../services/ThemeContext'
import { MainButton } from '../themed/MainButton'
import { ModalMessage } from '../themed/ModalParts'
import { ModalUi4 } from '../ui4/ModalUi4'
import { EdgeModal } from '../ui4/EdgeModal'

export interface ButtonInfo {
label: string
Expand Down Expand Up @@ -89,7 +89,7 @@ export function ButtonsModal<Buttons extends { [key: string]: ButtonInfo }>(
}

return (
<ModalUi4
<EdgeModal
warning={warning}
bridge={bridge}
title={title}
Expand Down Expand Up @@ -138,6 +138,6 @@ export function ButtonsModal<Buttons extends { [key: string]: ButtonInfo }>(
})}
</View>
</View>
</ModalUi4>
</EdgeModal>
)
}
6 changes: 3 additions & 3 deletions src/components/modals/ListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useFilter } from '../../hooks/useFilter'
import { useTheme } from '../services/ThemeContext'
import { FilledTextInput } from '../themed/FilledTextInput'
import { ModalFooter, ModalMessage } from '../themed/ModalParts'
import { ModalUi4 } from '../ui4/ModalUi4'
import { EdgeModal } from '../ui4/EdgeModal'

interface Props<T> {
bridge: AirshipBridge<any>
Expand Down Expand Up @@ -80,7 +80,7 @@ export function ListModal<T>({
}, [theme])

return (
<ModalUi4 title={title} bridge={bridge} onCancel={handleCancel}>
<EdgeModal title={title} bridge={bridge} onCancel={handleCancel}>
{message == null ? null : <ModalMessage>{message}</ModalMessage>}
{!textInput ? null : (
<FilledTextInput
Expand Down Expand Up @@ -111,6 +111,6 @@ export function ListModal<T>({
onScroll={() => Keyboard.dismiss()}
onViewableItemsChanged={onViewableItemsChanged}
/>
</ModalUi4>
</EdgeModal>
)
}
6 changes: 3 additions & 3 deletions src/components/modals/QrCodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { QrCode } from '../common/QrCode'
import { showError } from '../services/AirshipInstance'
import { Theme, useTheme } from '../services/ThemeContext'
import { ModalMessage } from '../themed/ModalParts'
import { ModalUi4 } from '../ui4/ModalUi4'
import { EdgeModal } from '../ui4/EdgeModal'

interface Props {
bridge: AirshipBridge<EdgeAccount | undefined>
Expand Down Expand Up @@ -103,7 +103,7 @@ export function QrCodeModal(props: Props) {
const handleCancel = useHandler(() => bridge.resolve(undefined))

return (
<ModalUi4
<EdgeModal
bridge={bridge}
onCancel={handleCancel}
title={lstrings.qr_modal_title}
Expand All @@ -121,7 +121,7 @@ export function QrCodeModal(props: Props) {
<QrCode key="qrcode" data={qrData} size={theme.rem(14)} />
)}
</View>
</ModalUi4>
</EdgeModal>
)
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/modals/RequestPermissionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { AirshipBridge } from 'react-native-airship'
import { lstrings } from '../../common/locales/strings'
import { Checkbox } from '../themed/Checkbox'
import { ModalMessage } from '../themed/ModalParts'
import { ButtonsViewUi4 } from '../ui4/ButtonsViewUi4'
import { ModalUi4 } from '../ui4/ModalUi4'
import { ButtonsView } from '../ui4/ButtonsView'
import { EdgeModal } from '../ui4/EdgeModal'

interface Props {
bridge: AirshipBridge<Required<PermissionsModalChoices> | undefined>
Expand Down Expand Up @@ -47,7 +47,7 @@ export function RequestPermissionsModal(props: Props) {
}

return (
<ModalUi4
<EdgeModal
bridge={bridge}
title={lstrings.security_is_our_priority_modal_title}
warning
Expand All @@ -69,7 +69,7 @@ export function RequestPermissionsModal(props: Props) {
>
{lstrings.notifications_opt_in_marketing}
</Checkbox>
<ButtonsViewUi4
<ButtonsView
primary={{
label: lstrings.enable,
onPress: () => handlePress('enable', true)
Expand All @@ -80,6 +80,6 @@ export function RequestPermissionsModal(props: Props) {
}}
parentType="modal"
/>
</ModalUi4>
</EdgeModal>
)
}
164 changes: 82 additions & 82 deletions src/components/modals/SecurityAlertsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
/**
* IMPORTANT: Changes in this file MUST be duplicated in edge-react-gui!
*/
import { EdgeLoginMessage } from 'edge-core-js'
import * as React from 'react'
import { Text } from 'react-native'
import { View } from 'react-native'
import { AirshipBridge } from 'react-native-airship'
import { cacheStyles } from 'react-native-patina'
import AntDesignIcon from 'react-native-vector-icons/AntDesign'
import FontAwesome from 'react-native-vector-icons/FontAwesome'
import { sprintf } from 'sprintf-js'
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'
import Ionicons from 'react-native-vector-icons/Ionicons'

import { lstrings } from '../../common/locales/strings'
import { useHandler } from '../../hooks/useHandler'
import { EdgeTouchableOpacity } from '../common/EdgeTouchableOpacity'
import { Theme, useTheme } from '../services/ThemeContext'
import { ModalScrollArea, ModalTitle } from '../themed/ModalParts'
import { ModalUi4 } from '../ui4/ModalUi4'
import { EdgeText, Paragraph } from '../themed/EdgeText'
import { ModalTitle } from '../themed/ModalParts'
import { EdgeCard } from '../ui4/EdgeCard'
import { EdgeModal } from '../ui4/EdgeModal'
import { SectionView } from '../ui4/SectionView'

interface Props {
bridge: AirshipBridge<string | undefined>
Expand All @@ -25,97 +23,99 @@ interface Props {
export const SecurityAlertsModal = (props: Props) => {
const { bridge, messages } = props
const theme = useTheme()
const styles = getStyles(theme)

const otpResetNames = messages
.filter(message => message.otpResetPending && message.username != null)
.map(message => message.username)
const voucherMessageNames = messages
.filter(
message => message.pendingVouchers.length > 0 && message.username != null
)
.map(message => message.username)

const handleCancel = useHandler(() => bridge.resolve(undefined))

const renderList = () => {
const out: React.ReactNode[] = []
const renderRow = (username: string, index: number) => {
return (
<EdgeCard
key={`${index}-${username}`}
onPress={() => bridge.resolve(username)}
>
<View style={styles.cardContainer}>
<EdgeText>{username}</EdgeText>
<FontAwesome5
name="chevron-right"
size={theme.rem(1.25)}
color={theme.iconTappable}
style={styles.chevron}
/>
</View>
</EdgeCard>
)
}

let isFirst = true
for (const message of messages) {
const { otpResetPending, username } = message
if (otpResetPending && username != null) {
out.push(renderRow(username, true, isFirst))
isFirst = false
}
}
for (const message of messages) {
const { pendingVouchers = [], username } = message
if (pendingVouchers.length > 0 && username != null) {
out.push(renderRow(username, false, isFirst))
isFirst = false
}
}
const renderLoginRequests = () => {
if (voucherMessageNames.length === 0) return null

return out
return (
<>
<Paragraph>{lstrings.login_attempt_detected}</Paragraph>
{voucherMessageNames.map((name, index) => renderRow(name ?? '', index))}
</>
)
}

const renderRow = (username: string, isReset: boolean, isFirst: boolean) => {
const styles = getStyles(theme)
// TODO: Pending removal after server endpoint removal
const renderOtpResetRequests = () => {
if (otpResetNames.length === 0) return null

return (
<EdgeTouchableOpacity
key={(isReset ? 'reset:' : 'voucher:') + username}
style={isFirst ? styles.row : styles.rowBorder}
onPress={() => bridge.resolve(username)}
>
<FontAwesome
color={isReset ? theme.dangerText : theme.warningText}
name="exclamation-triangle"
size={theme.rem(1.5)}
style={styles.rowIcon}
/>
<Text style={styles.rowText}>
<Text style={styles.bold}>
{isReset
? sprintf(lstrings.alert_modal_reset_s, username)
: sprintf(lstrings.alert_modal_voucher_s, username)}
</Text>
{lstrings.alert_modal_action}
</Text>
<AntDesignIcon
color={theme.iconTappable}
name="right"
size={theme.rem(1)}
style={styles.rowIcon}
/>
</EdgeTouchableOpacity>
<>
<Paragraph>{lstrings.twofa_attempt_detected}</Paragraph>
{otpResetNames.map((name, index) => renderRow(name ?? '', index))}
</>
)
}

return (
<ModalUi4 bridge={bridge} warning onCancel={handleCancel}>
<ModalTitle>{lstrings.security_is_our_priority_modal_title}</ModalTitle>
<ModalScrollArea>{renderList()}</ModalScrollArea>
</ModalUi4>
<EdgeModal
bridge={bridge}
scroll
title={
<ModalTitle
icon={
<Ionicons
name="warning"
size={theme.rem(1.75)}
color={theme.warningText}
/>
}
>
{lstrings.review_login_request}
</ModalTitle>
}
onCancel={handleCancel}
>
<SectionView marginRem={[0.5, 0, 0.5]}>
{renderLoginRequests()}
{renderOtpResetRequests()}
</SectionView>
</EdgeModal>
)
}

const getStyles = cacheStyles((theme: Theme) => ({
row: {
alignItems: 'center',
flexDirection: 'row'
},
rowBorder: {
alignItems: 'center',
borderTopColor: theme.lineDivider,
borderTopWidth: theme.thinLineWidth,
cardContainer: {
width: '100%',
flexDirection: 'row',
marginTop: theme.rem(0.5),
paddingTop: theme.rem(0.5)
},
rowIcon: {
margin: theme.rem(0.5)
},
rowText: {
color: theme.primaryText,
flexGrow: 1,
flexShrink: 1,
fontFamily: theme.fontFaceDefault,
fontSize: theme.rem(1),
alignItems: 'center',
justifyContent: 'space-between',
margin: theme.rem(0.5)
},
bold: {
fontWeight: 'bold'
chevron: {
alignSelf: 'center',
flexShrink: 0,
marginHorizontal: theme.rem(1)
}
}))
Loading
Loading