Skip to content

Commit

Permalink
Merge pull request #224 from EdgeApp/jon/ui4/login-request
Browse files Browse the repository at this point in the history
Jon/ui4/login-request
  • Loading branch information
Jon-edge authored Nov 12, 2024
2 parents ed60604 + 1530b1b commit aa91e8f
Show file tree
Hide file tree
Showing 23 changed files with 740 additions and 409 deletions.
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

0 comments on commit aa91e8f

Please sign in to comment.