feat: support receiving a new account via otpauth:// URI from the commandline in the UI

This change implements necessary control flow to pick up on accounts being passed to Keysmith via commandline options.

This change covers UX only for the happy flow case in which the received account is a valid otpauth:// URI.
If such an URI is passed to Keysmith, then the Add Account form is automatically pushed on the page stack and pre-populated with data received from the commandline.
With this UX, if the account is valid the user may either accept it immediately or tweak settings (most likely account name/issuer) to make it valid.

Issues: #7, #14
master
Johan Ouwerkerk 2020-09-30 21:50:12 +02:00
parent b0bc89810e
commit bc91de7e5b
1 changed files with 148 additions and 5 deletions

View File

@ -15,11 +15,15 @@ import org.kde.kirigami 2.4 as Kirigami
Kirigami.ApplicationWindow {
id: root
property bool addActionEnabled: true
property bool addActionEnabled: !addAccountRequested
property bool addAccountAvailable: false
property bool addAccountConfirmed: false
property bool addAccountRequested: CommandLine.newAccountRequested
property Models.AccountListModel accounts: Keysmith.accountListModel()
property Models.ValidatedAccountInput validatedInput: Models.ValidatedAccountInput {}
property Models.PasswordRequestModel passwordRequest: Keysmith.passwordRequest()
pageStack.initialPage: passwordRequest.previouslyDefined ? unlockAccountsPage : setupPasswordPage
property Component passwordRequestPage: passwordRequest.previouslyDefined ? unlockAccountsPage : setupPasswordPage
Component {
id: setupPasswordPage
@ -45,9 +49,90 @@ Kirigami.ApplicationWindow {
}
}
Component {
id: addFromCommandLinePageComponent
AddAccount {
quitEnabled: true
id: addAccountFromCommandLinePage
validateAccountAvailability: false
validatedInput: root.validatedInput
accounts: root.accounts
onQuit: {
pageStack.pop();
Qt.quit();
}
onCancelled: {
root.addAccountConfirmed = false;
popAddAccountPage(passwordRequestPage);
}
onNewAccount: {
root.addAccountConfirmed = true;
popAddAccountPage(passwordRequestPage);
}
}
}
Component {
id: renameFromCommandLinePageComponent
RenameAccount {
id: renameFromCommandLinePage
accounts: root.accounts
validatedInput: root.validatedInput
onCancelled: {
popAddAccountPage(accountsOverviewPage);
}
onNewAccount: {
root.accounts.addAccount(root.validatedInput);
popAddAccountPage(accountsOverviewPage);
}
}
}
Component {
id: invalidUriFromCommandLineErrorPage
ErrorPage {
quitEnabled: true
title: i18nc("@title:window", "Invalid account")
error: i18nc("@info:label", "The account you are trying to add is invalid. You can either quit the app, or continue without adding the account.")
onDismissed: {
popAddAccountPage(passwordRequestPage);
}
onQuit: {
pageStack.pop();
Qt.quit();
}
}
}
function autoAddNewAccountFromCommandLine() {
// accounts not yet loaded, keep the user waiting...
if (!accounts.loaded) {
return;
}
if (!passwordRequest.keyAvailable) {
return; // TODO warn if not
}
// nothing to do (anymore)
if (!root.addAccountConfirmed) {
return;
}
// claim the new account, try to see if it can be added directly or needs renaming/recovery
addAccountConfirmed = false;
if(accounts.isAccountStillAvailable(validatedInput.name, validatedInput.issuer)) {
accounts.addAccount(validatedInput);
pageStack.push(accountsOverviewPage);
} else {
pageStack.push(renameFromCommandLinePageComponent);
}
}
Component {
id: addPageComponent
AddAccount {
id: addAccountPage
accounts: root.accounts
onCancelled: {
popAddAccountPage();
@ -67,16 +152,74 @@ Kirigami.ApplicationWindow {
}
}
// TODO maybe have a onPasswordProvided handler to push a "progress" page to provide visual feedback for devices where key derivation is slow?
/*
* TODO maybe have a onPasswordProvided handler to push a "progress" page to provide visual feedback for devices
* where key derivation is slow?
*/
Connections {
target: passwordRequest
onDerivedKey : {
// TODO convert to C++ helper, have proper logging?
if (passwordRequest.keyAvailable) {
pageStack.pop();
if (!passwordRequest.keyAvailable) {
return; // TODO warn if not
}
pageStack.pop();
if (root.addAccountConfirmed) {
autoAddNewAccountFromCommandLine();
} else {
pageStack.push(accountsOverviewPage);
}
}
onPreviouslyDefinedChanged: {
/*
* Ignore if there is an account from the commandline to process: in that case password unlocking/setup is
* deferred until after account confirmation/rejection by the user
*/
if (!addAccountRequested) {
/*
* Cannot rely on passwordRequestPage property binding having been updated already here, work around
* by repeating the passwordRequestPage property binding expression instead.
*/
pageStack.push(passwordRequest.previouslyDefined ? unlockAccountsPage : setupPasswordPage);
}
}
}
Connections {
target: accounts
onLoadedChanged: {
if (accounts.loaded) {
autoAddNewAccountFromCommandLine();
}
}
}
Connections {
target: CommandLine
onNewAccountInvalid: {
// TODO properly report error in UX
if (addAccountRequested) {
addAccountAvailable = false;
addAccountConfirmed = false;
addActionEnabled = true;
pageStack.push(invalidUriFromCommandLineErrorPage);
}
// TODO warn if not
}
onNewAccountProcessed: {
addAccountAvailable = true;
pageStack.push(addFromCommandLinePageComponent);
}
}
Component.onCompleted: {
if (addAccountRequested) {
root.validatedInput.reset();
CommandLine.handleNewAccount(root.validatedInput);
}
}
}