Skip to content

Commit

Permalink
feat(Onboarding): implement the KeycardFactoryReset flow
Browse files Browse the repository at this point in the history
- integrate it into the UI and StoryBook
- a new keycardState is introduced: `FactoryResetting` (matching the
backend)
- a new store method introduced: `startKeycardFactoryReset()`
  • Loading branch information
caybro committed Jan 30, 2025
1 parent 9d17abb commit 88c35d7
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 19 deletions.
10 changes: 10 additions & 0 deletions storybook/pages/OnboardingLayoutPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Backpressure 0.1

import AppLayouts.Onboarding.enums 1.0
import AppLayouts.Onboarding2 1.0
Expand Down Expand Up @@ -96,6 +97,15 @@ SplitView {
addKeyPairState = Onboarding.AddKeyPairState.InProgress
}

function startKeycardFactoryReset() {
logs.logEvent("OnboardingStore.startKeycardFactoryReset")
console.warn("!!! SIMULATION: KEYCARD FACTORY RESET")
keycardState = Onboarding.KeycardState.FactoryResetting // SIMULATION: factory reset
Backpressure.debounce(root, 3000, () => {
keycardState = Onboarding.KeycardState.Empty
})()
}

// password
function getPasswordStrengthScore(password: string) { // -> int
logs.logEvent("OnboardingStore.getPasswordStrengthScore", ["password"], arguments)
Expand Down
1 change: 1 addition & 0 deletions ui/StatusQ/src/onboarding/enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class OnboardingEnums: public QObject
MaxPairingSlotsReached,
BlockedPIN, // PIN remaining attempts == 0
BlockedPUK, // PUK remaining attempts == 0
FactoryResetting,
// exit states
NotEmpty,
Empty
Expand Down
119 changes: 119 additions & 0 deletions ui/app/AppLayouts/Onboarding2/KeycardFactoryResetFlow.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import QtQuick 2.15
import QtQuick.Controls 2.15

import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Backpressure 0.1

import AppLayouts.Onboarding2.pages 1.0
import AppLayouts.Onboarding.enums 1.0

SQUtils.QObject {
id: root

required property StackView stackView

required property int keycardState
required property var tryToSetPinFunction
required property int remainingPinAttempts
required property int remainingPukAttempts

required property int keycardPinInfoPageDelay

signal keycardPinEntered(string pin)
signal unblockWithPukRequested
signal unblockWithSeedphraseRequested

signal performKeycardFactoryResetRequested

signal finished(bool fromLoginScreen)

function init(fromLoginScreen = false) {
d.fromLoginScreen = fromLoginScreen
root.stackView.push(d.initialComponent())
}

QtObject {
id: d

property bool fromLoginScreen

function initialComponent() {
if (root.keycardState === Onboarding.KeycardState.FactoryResetting)
return keycardResetPageComponent
return keycardResetAcks
}
}

Component {
id: keycardResetAcks

KeycardBasePage {
image.source: Theme.png("onboarding/keycard/reading")
title: qsTr("Factory reset Keycard")
subtitle: "<font color='%1'>".arg(Theme.palette.dangerColor1) +
qsTr("All data including the stored key pair and derived accounts will be removed from the Keycard") +
"</font>"
buttons: [
StatusCheckBox {
id: ack
width: Math.min(implicitWidth, parent.width)
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("I understand the key pair will be deleted")
},
Item {
width: parent.width
height: parent.spacing
},
StatusButton {
anchors.horizontalCenter: parent.horizontalCenter
type: StatusBaseButton.Type.Danger
text: qsTr("Factory reset this Keycard")
enabled: ack.checked
onClicked: {
root.performKeycardFactoryResetRequested()
root.stackView.replace(null, keycardResetPageComponent)
}
}
]
}
}

Component {
id: keycardResetPageComponent

KeycardBasePage {
id: keycardResetPage
readonly property bool resetting: root.keycardState === Onboarding.KeycardState.FactoryResetting

image.source: resetting ? Theme.png("onboarding/keycard/empty") // FIXME correct image
: Theme.png("onboarding/keycard/success")
title: resetting ? qsTr("Reseting Keycard") : qsTr("Keycard successfully factory reset")
subtitle: resetting ? "" : qsTr("You can now use this Keycard like it's a brand-new, empty Keycard")
infoText.text: resetting ? qsTr("Do not remove your Keycard or reader") : ""
buttons: [
Row {
spacing: 4
visible: keycardResetPage.resetting
anchors.horizontalCenter: parent.horizontalCenter
StatusLoadingIndicator {
color: Theme.palette.directColor1
}
StatusBaseText {
text: qsTr("Please wait while the Keycard is being reset")
}
},
StatusButton {
visible: !keycardResetPage.resetting
anchors.horizontalCenter: parent.horizontalCenter
width: 320
text: d.fromLoginScreen ? qsTr("Back to Login screen") : qsTr("Log in or Create profile")
onClicked: root.finished(d.fromLoginScreen)
}
]
}
}
}
27 changes: 22 additions & 5 deletions ui/app/AppLayouts/Onboarding2/OnboardingFlow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ SQUtils.QObject {
signal seedphraseSubmitted(string seedphrase)
signal setPasswordRequested(string password)
signal reloadKeycardRequested
signal keycardFactoryResetRequested
signal keyPairTransferRequested
signal performKeycardFactoryResetRequested
signal factoryResetDone(bool fromLoginScreen)

signal mnemonicWasShown()
signal mnemonicRemovalRequested()
Expand Down Expand Up @@ -201,7 +202,7 @@ SQUtils.QObject {
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay

onReloadKeycardRequested: root.reloadKeycardRequested()
onKeycardFactoryResetRequested: root.keycardFactoryResetRequested()
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()
onKeyPairTransferRequested: root.keyPairTransferRequested()
onKeycardPinCreated: (pin) => root.keycardPinCreated(pin)
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
Expand Down Expand Up @@ -267,7 +268,7 @@ SQUtils.QObject {
onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase)
onReloadKeycardRequested: root.reloadKeycardRequested()
onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init()
onKeycardFactoryResetRequested: root.keycardFactoryResetRequested()
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()
onUnblockWithPukRequested: unblockWithPukFlow.init()

onFinished: {
Expand All @@ -288,7 +289,7 @@ SQUtils.QObject {

onReloadKeycardRequested: root.reloadKeycardRequested()
onKeycardPinCreated: (pin) => root.keycardPinCreated(pin)
onKeycardFactoryResetRequested: root.keycardFactoryResetRequested()
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()

onFinished: (success) => {
if (success) {
Expand All @@ -312,7 +313,7 @@ SQUtils.QObject {
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay

onReloadKeycardRequested: root.reloadKeycardRequested()
onKeycardFactoryResetRequested: root.keycardFactoryResetRequested()
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init(true)
onKeyPairTransferRequested: root.keyPairTransferRequested()
onKeycardPinCreated: (pin) => root.keycardPinCreated(pin)
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
Expand All @@ -330,6 +331,22 @@ SQUtils.QObject {
onFinished: d.pushOrSkipBiometricsPage()
}

KeycardFactoryResetFlow {
id: keycardFactoryResetFlow
stackView: root.stackView
keycardState: root.keycardState
tryToSetPinFunction: root.tryToSetPinFunction
remainingPinAttempts: root.remainingPinAttempts
remainingPukAttempts: root.remainingPukAttempts
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay

onKeycardPinEntered: (pin) => root.keycardPinEntered(pin)
onUnblockWithPukRequested: unblockWithPukFlow.init()
onUnblockWithSeedphraseRequested: console.warn("!!! FIXME OnboardingFlow::onUnblockWithSeedphraseRequested")
onPerformKeycardFactoryResetRequested: root.performKeycardFactoryResetRequested()
onFinished: (fromLoginScreen) => root.factoryResetDone(fromLoginScreen)
}

Component {
id: enableBiometricsPage

Expand Down
11 changes: 8 additions & 3 deletions ui/app/AppLayouts/Onboarding2/OnboardingLayout.qml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ Page {
onReloadKeycardRequested: root.reloadKeycardRequested()
onMnemonicWasShown: root.onboardingStore.mnemonicWasShown()
onMnemonicRemovalRequested: root.onboardingStore.removeMnemonic()
onPerformKeycardFactoryResetRequested: root.onboardingStore.startKeycardFactoryReset()
onFactoryResetDone: (fromLoginScreen) => {
stack.clear()
if (fromLoginScreen)
stack.push(loginScreenComponent)
else
onboardingFlow.init()
}

onSyncProceedWithConnectionString: (connectionString) =>
root.onboardingStore.inputConnectionStringForBootstrapping(connectionString)
Expand All @@ -184,7 +192,6 @@ Page {
onEnableBiometricsRequested: (enabled) => d.enableBiometrics = enabled
onLinkActivated: (link) => Qt.openUrlExternally(link)
onFinished: (flow) => d.finishFlow(flow)
onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested")
}

Component {
Expand All @@ -206,7 +213,6 @@ Page {
d.selectedProfileKeyId = selectedProfileKeyId
unblockWithPukFlow.init()
}
onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested")
}
}

Expand All @@ -225,7 +231,6 @@ Page {
d.keycardPin = pin
root.onboardingStore.setPin(pin)
}
onKeycardFactoryResetRequested: console.warn("!!! FIXME OnboardingLayout::onKeycardFactoryResetRequested")

onFinished: (success) => {
if (success)
Expand Down
9 changes: 1 addition & 8 deletions ui/app/AppLayouts/Onboarding2/components/LoginKeycardBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ Control {

signal unblockWithSeedphraseRequested()
signal unblockWithPukRequested()
signal keycardFactoryResetRequested()

signal loginRequested(string pin)

Expand Down Expand Up @@ -86,16 +85,10 @@ Control {
}
MaybeOutlineButton {
width: parent.width
visible: root.keycardState === Onboarding.KeycardState.BlockedPIN
visible: root.keycardState === Onboarding.KeycardState.BlockedPIN || root.keycardState === Onboarding.KeycardState.BlockedPUK
text: qsTr("Unblock with recovery phrase")
onClicked: root.unblockWithSeedphraseRequested()
}
MaybeOutlineButton {
width: parent.width
visible: root.keycardState === Onboarding.KeycardState.BlockedPUK
text: qsTr("Factory reset Keycard")
onClicked: root.keycardFactoryResetRequested()
}
}
StatusPinInput {
Layout.alignment: Qt.AlignHCenter
Expand Down
11 changes: 10 additions & 1 deletion ui/app/AppLayouts/Onboarding2/pages/KeycardEnterPinPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ KeycardBasePage {
property var tryToSetPinFunction: (pin) => { console.error("tryToSetPinFunction: IMPLEMENT ME"); return false }
required property int remainingAttempts
property bool unblockWithPukAvailable
property string pinCorrectText: qsTr("PIN correct")

signal keycardPinEntered(string pin)
signal reloadKeycardRequested
Expand Down Expand Up @@ -134,18 +135,26 @@ KeycardBasePage {
target: errorText
visible: true
}
PropertyChanges {
target: image
source: Theme.png("onboarding/keycard/error")
}
},
State {
name: "success"
when: d.pinValid
PropertyChanges {
target: root
title: qsTr("PIN correct")
title: root.pinCorrectText
}
PropertyChanges {
target: pinInput
enabled: false
}
PropertyChanges {
target: image
source: Theme.png("onboarding/keycard/success")
}
StateChangeScript {
script: root.keycardPinEntered(pinInput.pinInput)
}
Expand Down
2 changes: 0 additions & 2 deletions ui/app/AppLayouts/Onboarding2/pages/LoginScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ OnboardingPage {
signal onboardingLoginFlowRequested()
signal unblockWithSeedphraseRequested()
signal unblockWithPukRequested()
signal keycardFactoryResetRequested()
signal lostKeycard()

QtObject {
Expand Down Expand Up @@ -227,7 +226,6 @@ OnboardingPage {
keycardRemainingPukAttempts: root.onboardingStore.keycardRemainingPukAttempts
onUnblockWithSeedphraseRequested: root.unblockWithSeedphraseRequested()
onUnblockWithPukRequested: root.unblockWithPukRequested()
onKeycardFactoryResetRequested: root.keycardFactoryResetRequested()
onPinEditedManually: {
// reset state when typing the PIN manually; not to break the bindings inside the component
d.resetBiometricsResult()
Expand Down
1 change: 1 addition & 0 deletions ui/app/AppLayouts/Onboarding2/qmldir
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CreateNewProfileFlow 1.0 CreateNewProfileFlow.qml
KeycardCreateProfileFlow 1.0 KeycardCreateProfileFlow.qml
KeycardCreateReplacementFlow 1.0 KeycardCreateReplacementFlow.qml
KeycardFactoryResetFlow 1.0 KeycardFactoryResetFlow.qml
LoginByKeycardFlow 1.0 LoginByKeycardFlow.qml
LoginBySyncingFlow 1.0 LoginBySyncingFlow.qml
OnboardingFlow 1.0 OnboardingFlow.qml
Expand Down
4 changes: 4 additions & 0 deletions ui/app/AppLayouts/Onboarding2/stores/OnboardingStore.qml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ QtObject {
d.onboardingModuleInst.startKeypairTransfer()
}

function startKeycardFactoryReset() {
d.onboardingModuleInst.startKeycardFactoryReset()
}

// password
signal accountLoginError(string error, bool wrongPassword)

Expand Down

0 comments on commit 88c35d7

Please sign in to comment.