feat: add login functionality
Add login functionality. If node accounts exist in status-go (keystores in ./data dir), then show the Login screen. Otherwise, show the Onboarding screen (generate keys screen). Update nim-stew to latest version. Change references to Address to the common type Account to prevent repeating of types. Distinguish between unknown and unhandled signals. Pass signals through to subscribers regardless if the signal type is known (in case the SignalSubscriber handles it with string comparison or other). Update serialization as much as possible Latest nim-stew updates allow type inheritance during de/serialization
This commit is contained in:
parent
dd9b4c50ea
commit
0f59529c57
|
@ -0,0 +1,43 @@
|
|||
import NimQml
|
||||
import ../../status/types as status_types
|
||||
import ../../signals/types
|
||||
import eventemitter
|
||||
import view
|
||||
import ../../models/accounts as AccountModel
|
||||
import chronicles
|
||||
import options
|
||||
import std/wrapnils
|
||||
|
||||
type LoginController* = ref object of SignalSubscriber
|
||||
view*: LoginView
|
||||
variant*: QVariant
|
||||
appEvents*: EventEmitter
|
||||
model: AccountModel
|
||||
|
||||
proc newController*(appEvents: EventEmitter): LoginController =
|
||||
result = LoginController()
|
||||
result.appEvents = appEvents
|
||||
result.model = newAccountModel()
|
||||
result.view = newLoginView(result.model)
|
||||
result.variant = newQVariant(result.view)
|
||||
|
||||
proc delete*(self: LoginController) =
|
||||
delete self.view
|
||||
delete self.variant
|
||||
|
||||
proc init*(self: LoginController, nodeAccounts: seq[NodeAccount]) =
|
||||
self.model.nodeAccounts = nodeAccounts
|
||||
for nodeAccount in nodeAccounts:
|
||||
self.view.addAccountToList(nodeAccount)
|
||||
|
||||
proc handleNodeLogin(self: LoginController, data: Signal) =
|
||||
var response = NodeSignal(data)
|
||||
self.view.setLastLoginResponse($response.event.toJson)
|
||||
if ?.response.event.error == "" and self.model.currentAccount != nil:
|
||||
self.appEvents.emit("login", AccountArgs(account: self.model.currentAccount))
|
||||
|
||||
method onSignal(self: LoginController, data: Signal) =
|
||||
if data.signalType == SignalType.NodeLogin:
|
||||
self.handleNodeLogin(data)
|
||||
else:
|
||||
discard
|
|
@ -0,0 +1,86 @@
|
|||
import NimQml
|
||||
import Tables
|
||||
import json
|
||||
import nimcrypto
|
||||
import ../../signals/types
|
||||
import ../../status/types as status_types
|
||||
import ../../status/accounts as status_accounts
|
||||
import strformat
|
||||
import json_serialization
|
||||
import core
|
||||
import ../../models/accounts as AccountModel
|
||||
|
||||
type
|
||||
AccountRoles {.pure.} = enum
|
||||
Username = UserRole + 1,
|
||||
Identicon = UserRole + 2,
|
||||
Key = UserRole + 3
|
||||
|
||||
QtObject:
|
||||
type LoginView* = ref object of QAbstractListModel
|
||||
accounts: seq[NodeAccount]
|
||||
lastLoginResponse: string
|
||||
model*: AccountModel
|
||||
|
||||
proc setup(self: LoginView) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc delete*(self: LoginView) =
|
||||
self.QAbstractListModel.delete
|
||||
self.accounts = @[]
|
||||
|
||||
proc newLoginView*(model: AccountModel): LoginView =
|
||||
new(result, delete)
|
||||
result.accounts = @[]
|
||||
result.lastLoginResponse = ""
|
||||
result.model = model
|
||||
result.setup
|
||||
|
||||
proc addAccountToList*(self: LoginView, account: NodeAccount) =
|
||||
self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len)
|
||||
self.accounts.add(account)
|
||||
self.endInsertRows()
|
||||
|
||||
method rowCount(self: LoginView, index: QModelIndex = nil): int =
|
||||
return self.accounts.len
|
||||
|
||||
method data(self: LoginView, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.accounts.len:
|
||||
return
|
||||
|
||||
let asset = self.accounts[index.row]
|
||||
let assetRole = role.AccountRoles
|
||||
case assetRole:
|
||||
of AccountRoles.Username: result = newQVariant(asset.name)
|
||||
of AccountRoles.Identicon: result = newQVariant(asset.photoPath)
|
||||
of AccountRoles.Key: result = newQVariant(asset.keyUid)
|
||||
|
||||
method roleNames(self: LoginView): Table[int, string] =
|
||||
{ AccountRoles.Username.int:"username",
|
||||
AccountRoles.Identicon.int:"identicon",
|
||||
AccountRoles.Key.int:"key" }.toTable
|
||||
|
||||
proc login(self: LoginView, selectedAccountIndex: int, password: string): string {.slot.} =
|
||||
try:
|
||||
result = self.model.login(selectedAccountIndex, password).toJson
|
||||
except:
|
||||
let
|
||||
e = getCurrentException()
|
||||
msg = getCurrentExceptionMsg()
|
||||
result = SignalError(error: msg).toJson
|
||||
|
||||
proc lastLoginResponse*(self: LoginView): string =
|
||||
result = self.lastLoginResponse
|
||||
|
||||
proc loginResponseChanged*(self: LoginView, response: string) {.signal.}
|
||||
|
||||
proc setLastLoginResponse*(self: LoginView, loginResponse: string) {.slot.} =
|
||||
self.lastLoginResponse = loginResponse
|
||||
self.loginResponseChanged(loginResponse)
|
||||
|
||||
QtProperty[string] loginResponse:
|
||||
read = lastLoginResponse
|
||||
write = setLastLoginResponse
|
||||
notify = loginResponseChanged
|
|
@ -1,14 +1,18 @@
|
|||
import NimQml
|
||||
import ../../models/accounts
|
||||
import ../../signals/types
|
||||
import ../../status/types as status_types
|
||||
import ../../status/accounts as status_accounts
|
||||
import ../../models/accounts as AccountModel
|
||||
import eventemitter
|
||||
import view
|
||||
import chronicles
|
||||
import ../../signals/types
|
||||
import std/wrapnils
|
||||
|
||||
type OnboardingController* = object
|
||||
type OnboardingController* = ref object of SignalSubscriber
|
||||
view*: OnboardingView
|
||||
variant*: QVariant
|
||||
model*: AccountModel
|
||||
appEvents*: EventEmitter
|
||||
model: AccountModel
|
||||
|
||||
proc newController*(appEvents: EventEmitter): OnboardingController =
|
||||
result = OnboardingController()
|
||||
|
@ -16,8 +20,6 @@ proc newController*(appEvents: EventEmitter): OnboardingController =
|
|||
result.model = newAccountModel()
|
||||
result.view = newOnboardingView(result.model)
|
||||
result.variant = newQVariant(result.view)
|
||||
result.model.events.on("accountsReady") do(a: Args):
|
||||
appEvents.emit("accountsReady", a)
|
||||
|
||||
proc delete*(self: OnboardingController) =
|
||||
delete self.view
|
||||
|
@ -25,6 +27,17 @@ proc delete*(self: OnboardingController) =
|
|||
|
||||
proc init*(self: OnboardingController) =
|
||||
let accounts = self.model.generateAddresses()
|
||||
|
||||
for account in accounts:
|
||||
self.view.addAddressToList(account.toAddress())
|
||||
self.view.addAccountToList(account)
|
||||
|
||||
proc handleNodeLogin(self: OnboardingController, data: Signal) =
|
||||
var response = NodeSignal(data)
|
||||
self.view.setLastLoginResponse($response.event.toJson)
|
||||
if ?.response.event.error == "" and self.model.currentAccount != nil:
|
||||
self.appEvents.emit("login", AccountArgs(account: self.model.currentAccount))
|
||||
|
||||
method onSignal(self: OnboardingController, data: Signal) =
|
||||
if data.signalType == SignalType.NodeLogin:
|
||||
self.handleNodeLogin(data)
|
||||
else:
|
||||
discard
|
||||
|
|
|
@ -2,61 +2,83 @@ import NimQml
|
|||
import Tables
|
||||
import json
|
||||
import nimcrypto
|
||||
import ../../models/accounts as Models
|
||||
import ../../status/types as status_types
|
||||
import ../../signals/types
|
||||
import strformat
|
||||
import json_serialization
|
||||
import ../../models/accounts as AccountModel
|
||||
|
||||
type
|
||||
AddressRoles {.pure.} = enum
|
||||
AccountRoles {.pure.} = enum
|
||||
Username = UserRole + 1,
|
||||
Identicon = UserRole + 2,
|
||||
Key = UserRole + 3
|
||||
|
||||
QtObject:
|
||||
type OnboardingView* = ref object of QAbstractListModel
|
||||
addresses*: seq[Address]
|
||||
model: AccountModel
|
||||
accounts*: seq[GeneratedAccount]
|
||||
lastLoginResponse: string
|
||||
model*: AccountModel
|
||||
|
||||
proc setup(self: OnboardingView) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc delete*(self: OnboardingView) =
|
||||
self.QAbstractListModel.delete
|
||||
self.addresses = @[]
|
||||
self.accounts = @[]
|
||||
|
||||
proc newOnboardingView*(model: AccountModel): OnboardingView =
|
||||
new(result, delete)
|
||||
result.accounts = @[]
|
||||
result.lastLoginResponse = ""
|
||||
result.model = model
|
||||
result.addresses = @[]
|
||||
result.setup
|
||||
|
||||
proc addAddressToList*(self: OnboardingView, address: Address) =
|
||||
self.beginInsertRows(newQModelIndex(), self.addresses.len, self.addresses.len)
|
||||
self.addresses.add(address)
|
||||
proc addAccountToList*(self: OnboardingView, account: GeneratedAccount) =
|
||||
self.beginInsertRows(newQModelIndex(), self.accounts.len, self.accounts.len)
|
||||
self.accounts.add(account)
|
||||
self.endInsertRows()
|
||||
|
||||
method rowCount(self: OnboardingView, index: QModelIndex = nil): int =
|
||||
return self.addresses.len
|
||||
return self.accounts.len
|
||||
|
||||
method data(self: OnboardingView, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.addresses.len:
|
||||
if index.row < 0 or index.row >= self.accounts.len:
|
||||
return
|
||||
|
||||
let asset = self.addresses[index.row]
|
||||
let assetRole = role.AddressRoles
|
||||
let asset = self.accounts[index.row]
|
||||
let assetRole = role.AccountRoles
|
||||
case assetRole:
|
||||
of AddressRoles.Username: result = newQVariant(asset.username)
|
||||
of AddressRoles.Identicon: result = newQVariant(asset.identicon)
|
||||
of AddressRoles.Key: result = newQVariant(asset.key)
|
||||
of AccountRoles.Username: result = newQVariant(asset.name)
|
||||
of AccountRoles.Identicon: result = newQVariant(asset.photoPath)
|
||||
of AccountRoles.Key: result = newQVariant(asset.keyUid)
|
||||
|
||||
method roleNames(self: OnboardingView): Table[int, string] =
|
||||
{ AddressRoles.Username.int:"username",
|
||||
AddressRoles.Identicon.int:"identicon",
|
||||
AddressRoles.Key.int:"key" }.toTable
|
||||
{ AccountRoles.Username.int:"username",
|
||||
AccountRoles.Identicon.int:"identicon",
|
||||
AccountRoles.Key.int:"key" }.toTable
|
||||
|
||||
proc storeAccountAndLogin(self: OnboardingView, selectedAccountIndex: int, password: string) {.slot.} =
|
||||
discard self.model.storeAccountAndLogin(selectedAccountIndex, password)
|
||||
proc storeAccountAndLogin(self: OnboardingView, selectedAccountIndex: int, password: string): string {.slot.} =
|
||||
try:
|
||||
result = self.model.storeAccountAndLogin(selectedAccountIndex, password).toJson
|
||||
except:
|
||||
let
|
||||
e = getCurrentException()
|
||||
msg = getCurrentExceptionMsg()
|
||||
result = SignalError(error: msg).toJson
|
||||
|
||||
# TODO: this is temporary and will be removed once accounts import and creation is working
|
||||
proc generateRandomAccountAndLogin*(self: OnboardingView) {.slot.} =
|
||||
self.model.generateRandomAccountAndLogin()
|
||||
proc lastLoginResponse*(self: OnboardingView): string =
|
||||
result = self.lastLoginResponse
|
||||
|
||||
proc loginResponseChanged*(self: OnboardingView, response: string) {.signal.}
|
||||
|
||||
proc setLastLoginResponse*(self: OnboardingView, loginResponse: string) {.slot.} =
|
||||
self.lastLoginResponse = loginResponse
|
||||
self.loginResponseChanged(loginResponse)
|
||||
|
||||
QtProperty[string] loginResponse:
|
||||
read = lastLoginResponse
|
||||
write = setLastLoginResponse
|
||||
notify = loginResponseChanged
|
||||
|
|
|
@ -1,28 +1,16 @@
|
|||
import eventemitter
|
||||
import json_serialization
|
||||
import ../status/accounts as status_accounts
|
||||
import ../status/types
|
||||
|
||||
type
|
||||
Address* = ref object
|
||||
username*, identicon*, key*: string
|
||||
|
||||
proc toAddress*(account: GeneratedAccount): Address =
|
||||
result = Address(username: account.name, identicon: account.photoPath, key: account.address)
|
||||
import options
|
||||
|
||||
type
|
||||
AccountModel* = ref object
|
||||
generatedAddresses*: seq[GeneratedAccount]
|
||||
events*: EventEmitter
|
||||
nodeAccounts*: seq[NodeAccount]
|
||||
currentAccount*: Account
|
||||
|
||||
proc newAccountModel*(): AccountModel =
|
||||
result = AccountModel()
|
||||
result.events = createEventEmitter()
|
||||
result.generatedAddresses = @[]
|
||||
|
||||
proc delete*(self: AccountModel) =
|
||||
# delete self.generatedAddresses
|
||||
discard
|
||||
result.currentAccount = nil
|
||||
|
||||
proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
|
||||
var accounts = status_accounts.generateAddresses()
|
||||
|
@ -32,13 +20,12 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
|
|||
self.generatedAddresses.add(account)
|
||||
self.generatedAddresses
|
||||
|
||||
# TODO: this is temporary and will be removed once accounts import and creation is working
|
||||
proc generateRandomAccountAndLogin*(self: AccountModel) =
|
||||
let generatedAccounts = status_accounts.generateAddresses()
|
||||
let account = status_accounts.setupAccount(generatedAccounts[0], "qwerty")
|
||||
self.events.emit("accountsReady", AccountArgs(account: account))
|
||||
proc login*(self: AccountModel, selectedAccountIndex: int, password: string): NodeAccount =
|
||||
let currentNodeAccount = self.nodeAccounts[selectedAccountIndex]
|
||||
self.currentAccount = currentNodeAccount.toAccount
|
||||
result = status_accounts.login(currentNodeAccount, password)
|
||||
|
||||
proc storeAccountAndLogin*(self: AccountModel, selectedAccountIndex: int, password: string): Account =
|
||||
let generatedAccount: GeneratedAccount = self.generatedAddresses[selectedAccountIndex]
|
||||
result = status_accounts.setupAccount(generatedAccount, password)
|
||||
self.events.emit("accountsReady", AccountArgs(account: result))
|
||||
self.currentAccount = generatedAccount.toAccount
|
|
@ -1,4 +1,5 @@
|
|||
import eventemitter
|
||||
import ../status/types
|
||||
|
||||
type
|
||||
MailServer* = ref object
|
||||
|
@ -11,5 +12,5 @@ type
|
|||
type Profile* = ref object
|
||||
username*, identicon*: string
|
||||
|
||||
proc toProfileModel*(obj: object): Profile =
|
||||
result = Profile(username: obj.name, identicon: obj.photoPath)
|
||||
proc toProfileModel*(account: Account): Profile =
|
||||
result = Profile(username: account.name, identicon: account.photoPath)
|
||||
|
|
|
@ -6,13 +6,12 @@ import app/node/core as node
|
|||
import app/profile/core as profile
|
||||
import signals/core as signals
|
||||
import app/onboarding/core as onboarding
|
||||
import app/login/core as login
|
||||
import state
|
||||
import status/accounts as status_accounts
|
||||
import status/core as status_core
|
||||
import status/chat as status_chat
|
||||
import status/types as types
|
||||
import status/libstatus
|
||||
import models/accounts
|
||||
import state
|
||||
import status/types
|
||||
import eventemitter
|
||||
|
@ -24,7 +23,7 @@ logScope:
|
|||
topics = "main"
|
||||
|
||||
proc mainProc() =
|
||||
let nodeAccounts = Json.decode(status_accounts.initNodeAccounts(), seq[NodeAccount]) # to be used for login
|
||||
let nodeAccounts = status_accounts.initNodeAccounts()
|
||||
let app = newQApplication()
|
||||
let engine = newQQmlApplicationEngine()
|
||||
let signalController = signals.newController(app)
|
||||
|
@ -56,23 +55,33 @@ proc mainProc() =
|
|||
var profile = profile.newController(appEvents)
|
||||
engine.setRootContextProperty("profileModel", profile.variant)
|
||||
|
||||
# var accountsModel = newAccountModel()
|
||||
appEvents.on("accountsReady") do(a: Args):
|
||||
appEvents.on("login") do(a: Args):
|
||||
var args = AccountArgs(a)
|
||||
status_core.startMessenger()
|
||||
wallet.init()
|
||||
profile.init(args.account) # TODO: use correct account
|
||||
profile.init(args.account)
|
||||
|
||||
# var onboarding = onboarding.newController(accountsModel)
|
||||
var login = login.newController(appEvents)
|
||||
var onboarding = onboarding.newController(appEvents)
|
||||
onboarding.init()
|
||||
engine.setRootContextProperty("onboardingModel", onboarding.variant)
|
||||
|
||||
# TODO: replace this with routing
|
||||
let showLogin = nodeAccounts.len > 0
|
||||
engine.setRootContextProperty("showLogin", newQVariant(showLogin))
|
||||
|
||||
if nodeAccounts.len > 0:
|
||||
login.init(nodeAccounts)
|
||||
engine.setRootContextProperty("loginModel", login.variant)
|
||||
else:
|
||||
onboarding.init()
|
||||
engine.setRootContextProperty("onboardingModel", onboarding.variant)
|
||||
|
||||
signalController.init()
|
||||
signalController.addSubscriber(SignalType.Wallet, wallet)
|
||||
signalController.addSubscriber(SignalType.Wallet, node)
|
||||
signalController.addSubscriber(SignalType.Message, chat)
|
||||
signalController.addSubscriber(SignalType.WhisperFilterAdded, chat)
|
||||
signalController.addSubscriber(SignalType.NodeLogin, login)
|
||||
signalController.addSubscriber(SignalType.NodeLogin, onboarding)
|
||||
|
||||
engine.setRootContextProperty("signals", signalController.variant)
|
||||
|
||||
|
@ -81,8 +90,8 @@ proc mainProc() =
|
|||
chat.load(channel.name)
|
||||
)
|
||||
|
||||
# accountsModel.appEvents.on("accountsReady") do(a: Args):
|
||||
# appEvents.on("accountsReady") do(a: Args):
|
||||
# accountsModel.appEvents.on("login") do(a: Args):
|
||||
# appEvents.on("login") do(a: Args):
|
||||
# appState.addChannel("test")
|
||||
# appState.addChannel("test2")
|
||||
# appState.addChannel("status")
|
||||
|
|
|
@ -6,6 +6,8 @@ import types
|
|||
import messages
|
||||
import chronicles
|
||||
import whisperFilter
|
||||
import strutils
|
||||
import json_serialization
|
||||
|
||||
logScope:
|
||||
topics = "signals"
|
||||
|
@ -45,27 +47,35 @@ QtObject:
|
|||
let jsonSignal = (self.statusSignal).parseJson
|
||||
let signalString = $jsonSignal["type"].getStr
|
||||
|
||||
var signalType: SignalType
|
||||
var signal: Signal
|
||||
trace "Raw signal data", data = $jsonSignal
|
||||
|
||||
case signalString:
|
||||
of "messages.new":
|
||||
signalType = SignalType.Message
|
||||
var signalType: SignalType
|
||||
|
||||
try:
|
||||
signalType = parseEnum[SignalType](signalString)
|
||||
except:
|
||||
warn "Unknown signal received", type = signalString
|
||||
signalType = SignalType.Unknown
|
||||
return
|
||||
|
||||
var signal: Signal = Signal(signalType: signalType)
|
||||
|
||||
case signalType:
|
||||
of SignalType.Message:
|
||||
signal = messages.fromEvent(jsonSignal)
|
||||
of "whisper.filter.added":
|
||||
signalType = SignalType.WhisperFilterAdded
|
||||
of SignalType.WhisperFilterAdded:
|
||||
signal = whisperFilter.fromEvent(jsonSignal)
|
||||
of "wallet":
|
||||
signalType = SignalType.Wallet
|
||||
of SignalType.Wallet:
|
||||
signal = WalletSignal(content: $jsonSignal)
|
||||
of SignalType.NodeLogin:
|
||||
signal = Json.decode($jsonSignal, NodeSignal)
|
||||
else:
|
||||
warn "Unhandled signal received", type = signalString
|
||||
signalType = SignalType.Unknown
|
||||
return
|
||||
discard
|
||||
|
||||
signal.signalType = signalType
|
||||
|
||||
if not self.signalSubscribers.hasKey(signalType):
|
||||
warn "Unhandled signal received", type = signalString
|
||||
self.signalSubscribers[signalType] = @[]
|
||||
|
||||
for subscriber in self.signalSubscribers[signalType]:
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import chronicles
|
||||
import ../status/types
|
||||
import json_serialization
|
||||
|
||||
type SignalSubscriber* = ref object of RootObj
|
||||
|
||||
type Signal* = ref object of RootObj
|
||||
signalType*: SignalType
|
||||
signalType* {.serializedFieldName("type").}: SignalType
|
||||
|
||||
type SignalError* = object
|
||||
error*: string
|
||||
|
||||
type NodeSignal* = ref object of Signal
|
||||
event*: SignalError
|
||||
|
||||
type WalletSignal* = ref object of Signal
|
||||
content*: string
|
||||
|
|
|
@ -34,7 +34,7 @@ proc ensureDir(dirname: string) =
|
|||
# removeDir(dirname)
|
||||
createDir(dirname)
|
||||
|
||||
proc initNodeAccounts*(): string =
|
||||
proc initNodeAccounts*(): seq[NodeAccount] =
|
||||
const datadir = "./data/"
|
||||
const keystoredir = "./data/keystore/"
|
||||
const nobackupdir = "./noBackup/"
|
||||
|
@ -44,9 +44,17 @@ proc initNodeAccounts*(): string =
|
|||
ensureDir(nobackupdir)
|
||||
|
||||
discard $libstatus.initKeystore(keystoredir);
|
||||
result = $libstatus.openAccounts(datadir);
|
||||
let strNodeAccounts = $libstatus.openAccounts(datadir);
|
||||
result = Json.decode(strNodeAccounts, seq[NodeAccount])
|
||||
|
||||
proc saveAccountAndLogin*(multiAccounts: MultiAccounts, alias: string, identicon: string, accountData: string, password: string, configJSON: string, settingsJSON: string): Account =
|
||||
proc saveAccountAndLogin*(
|
||||
multiAccounts: MultiAccounts,
|
||||
alias: string,
|
||||
identicon: string,
|
||||
accountData: string,
|
||||
password: string,
|
||||
configJSON: string,
|
||||
settingsJSON: string): Account =
|
||||
let hashedPassword = "0x" & $keccak_256.digest(password)
|
||||
let subaccountData = %* [
|
||||
{
|
||||
|
@ -69,11 +77,14 @@ proc saveAccountAndLogin*(multiAccounts: MultiAccounts, alias: string, identicon
|
|||
|
||||
var savedResult = $libstatus.saveAccountAndLogin(accountData, hashedPassword, settingsJSON, configJSON, $subaccountData)
|
||||
let parsedSavedResult = savedResult.parseJson
|
||||
let error = parsedSavedResult["error"].getStr
|
||||
|
||||
if parsedSavedResult["error"].getStr == "":
|
||||
if error == "":
|
||||
debug "Account saved succesfully"
|
||||
result = Account(name: alias, photoPath: identicon)
|
||||
return
|
||||
|
||||
result = Account(name: alias, photoPath: identicon)
|
||||
raise newException(LoginError, "Error saving account and logging in: " & error)
|
||||
|
||||
proc generateMultiAccounts*(account: GeneratedAccount, password: string): MultiAccounts =
|
||||
let hashedPassword = "0x" & $keccak_256.digest(password)
|
||||
|
@ -135,3 +146,16 @@ proc setupAccount*(account: GeneratedAccount, password: string): Account =
|
|||
# TODO this is needed for now for the retrieving of past messages. We'll either move or remove it later
|
||||
let peer = "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443"
|
||||
discard libstatus.addPeer(peer)
|
||||
|
||||
proc login*(nodeAccount: NodeAccount, password: string): NodeAccount =
|
||||
let hashedPassword = "0x" & $keccak_256.digest(password)
|
||||
let account = nodeAccount.toAccount
|
||||
let loginResult = $libstatus.login($toJson(account), hashedPassword)
|
||||
let error = parseJson(loginResult)["error"].getStr
|
||||
|
||||
if error == "":
|
||||
debug "Login requested", user=nodeAccount.name
|
||||
result = nodeAccount
|
||||
return
|
||||
|
||||
raise newException(LoginError, "Error logging in: " & error)
|
||||
|
|
|
@ -25,3 +25,5 @@ proc sendTransaction*(jsonArgs: cstring, password: cstring): cstring {.importc:
|
|||
proc generateAlias*(p0: GoString): cstring {.importc: "GenerateAlias".}
|
||||
|
||||
proc identicon*(p0: GoString): cstring {.importc: "Identicon".}
|
||||
|
||||
proc login*(acctData: cstring, password: cstring): cstring {.importc: "Login".}
|
||||
|
|
|
@ -7,13 +7,16 @@ type SignalCallback* = proc(eventMessage: cstring): void {.cdecl.}
|
|||
type SignalType* {.pure.} = enum
|
||||
Message = "messages.new"
|
||||
Wallet = "wallet"
|
||||
NodeReady = "node.ready"
|
||||
NodeStarted = "node.started"
|
||||
NodeLogin = "node.login"
|
||||
EnvelopeSent = "envelope.sent"
|
||||
EnvelopeExpired = "envelope.expired"
|
||||
MailserverRequestCompleted = "mailserver.request.completed"
|
||||
MailserverRequestExpired = "mailserver.request.expired"
|
||||
DiscoverSummary = "discover.summary"
|
||||
DiscoveryStarted = "discovery.started"
|
||||
DiscoveryStopped = "discovery.stopped"
|
||||
DiscoverySummary = "discovery.summary"
|
||||
SubscriptionsData = "subscriptions.data"
|
||||
SubscriptionsError = "subscriptions.error"
|
||||
WhisperFilterAdded = "whisper.filter.added"
|
||||
|
@ -36,31 +39,38 @@ type MultiAccounts* = object
|
|||
|
||||
|
||||
type
|
||||
Account* = object of RootObj
|
||||
Account* = ref object of RootObj
|
||||
name*: string
|
||||
keyUid* {.serializedFieldName("key-uid").}: string
|
||||
photoPath* {.serializedFieldName("photo-path").}: string
|
||||
|
||||
type
|
||||
NodeAccount* = object
|
||||
NodeAccount* = ref object of Account
|
||||
timestamp*: int
|
||||
keycardPairing* {.serializedFieldName("keycard-pairing").}: string
|
||||
# deserialisation does not handle base classes, so flatten
|
||||
name*: string
|
||||
keyUid* {.serializedFieldName("key-uid").}: string
|
||||
photoPath* {.serializedFieldName("photo-path").}: string
|
||||
|
||||
type
|
||||
GeneratedAccount* = object
|
||||
GeneratedAccount* = ref object
|
||||
publicKey*: string
|
||||
address*: string
|
||||
id*: string
|
||||
mnemonic*: string
|
||||
derived*: MultiAccounts
|
||||
# deserialisation does not handle base classes, so flatten
|
||||
# FIXME: should inherit from Account but multiAccountGenerateAndDeriveAddresses
|
||||
# response has a camel-cased properties like "publicKey" and "keyUid", so the
|
||||
# serializedFieldName pragma would need to be different
|
||||
name*: string
|
||||
keyUid*: string
|
||||
photoPath*: string
|
||||
|
||||
proc toAccount*(account: GeneratedAccount): Account =
|
||||
result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.address)
|
||||
|
||||
proc toAccount*(account: NodeAccount): Account =
|
||||
result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.keyUid)
|
||||
|
||||
type AccountArgs* = ref object of Args
|
||||
account*: Account
|
||||
|
||||
type
|
||||
LoginError* = object of Exception
|
||||
|
|
|
@ -9,10 +9,8 @@ SwipeView {
|
|||
anchors.fill: parent
|
||||
currentIndex: 0
|
||||
|
||||
signal loginDone
|
||||
|
||||
onCurrentItemChanged: {
|
||||
currentItem.txtPassword.focus = true
|
||||
currentItem.txtPassword.focus = true;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -87,17 +85,16 @@ SwipeView {
|
|||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Select"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
console.log("button: " + wizardStep2.selectedIndex)
|
||||
|
||||
swipeView.incrementCurrentIndex()
|
||||
}
|
||||
Button {
|
||||
text: "Select"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
swipeView.incrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,9 +132,7 @@ SwipeView {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
console.log("password: " + txtPassword.text)
|
||||
|
||||
swipeView.incrementCurrentIndex()
|
||||
swipeView.incrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,9 +173,9 @@ SwipeView {
|
|||
icon: StandardIcon.Warning
|
||||
standardButtons: StandardButton.Ok
|
||||
onAccepted: {
|
||||
txtConfirmPassword.clear()
|
||||
swipeView.currentIndex = 1
|
||||
txtPassword.focus = true
|
||||
txtConfirmPassword.clear();
|
||||
swipeView.currentIndex = 1;
|
||||
txtPassword.focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,27 +183,32 @@ SwipeView {
|
|||
id: storeAccountAndLoginError
|
||||
title: "Error storing account and logging in"
|
||||
text: "An error occurred while storing your account and logging in: "
|
||||
icon: StandardIcon.Warning
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: onboardingModel
|
||||
onLoginResponseChanged: {
|
||||
const loginResponse = JSON.parse(response);
|
||||
if(loginResponse.error){
|
||||
storeAccountAndLoginError.text += loginResponse.error;
|
||||
storeAccountAndLoginError.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Finish"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
console.log("confirm clicked " + txtConfirmPassword.text + " : " + txtPassword.text)
|
||||
|
||||
if (txtConfirmPassword.text != txtPassword.text) {
|
||||
return passwordsDontMatchError.open()
|
||||
return passwordsDontMatchError.open();
|
||||
}
|
||||
|
||||
const selectedAccountIndex = wizardStep2.selectedIndex
|
||||
onboardingModel.storeAccountAndLogin(selectedAccountIndex,
|
||||
txtPassword.text)
|
||||
|
||||
swipeView.loginDone()
|
||||
onboardingModel.storeAccountAndLogin(selectedAccountIndex, txtPassword.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,21 +29,6 @@ RowLayout {
|
|||
Item {
|
||||
id: itmSlide1
|
||||
|
||||
StyledButton {
|
||||
id: btnGenRandomAcct
|
||||
width: 250
|
||||
height: 50
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 50
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
label: "Generate random account and login"
|
||||
onClicked: {
|
||||
onboardingModel.generateRandomAccountAndLogin()
|
||||
app.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.4
|
||||
import QtQuick.Layouts 1.11
|
||||
import QtQuick.Window 2.11
|
||||
import QtQuick.Dialogs 1.3
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
currentIndex: 0
|
||||
|
||||
signal loginDone(response: var)
|
||||
|
||||
onCurrentItemChanged: {
|
||||
currentItem.txtPassword.focus = true;
|
||||
}
|
||||
|
||||
Item {
|
||||
id: wizardStep2
|
||||
property int selectedIndex: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
width: 620
|
||||
height: 427
|
||||
|
||||
Text {
|
||||
text: "Login"
|
||||
font.pointSize: 36
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
ButtonGroup {
|
||||
id: accountGroup
|
||||
}
|
||||
|
||||
Component {
|
||||
id: addressViewDelegate
|
||||
|
||||
Item {
|
||||
height: 56
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
|
||||
Row {
|
||||
RadioButton {
|
||||
checked: index == 0 ? true : false
|
||||
ButtonGroup.group: accountGroup
|
||||
onClicked: {
|
||||
wizardStep2.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Image {
|
||||
source: identicon
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Text {
|
||||
text: username
|
||||
}
|
||||
Text {
|
||||
text: key
|
||||
width: 160
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: addressesView
|
||||
contentWidth: 200
|
||||
model: loginModel
|
||||
delegate: addressViewDelegate
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
anchors.topMargin: 36
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Select"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
swipeView.incrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: wizardStep3
|
||||
property Item txtPassword: txtPassword
|
||||
|
||||
Text {
|
||||
text: "Enter password"
|
||||
font.pointSize: 36
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "#EEEEEE"
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.centerIn: parent
|
||||
height: 32
|
||||
width: parent.width - 40
|
||||
TextInput {
|
||||
id: txtPassword
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
echoMode: TextInput.Password
|
||||
selectByMouse: true
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: loginError
|
||||
title: "Login failed"
|
||||
text: "Login failed. Please re-enter your password and try again."
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
onAccepted: {
|
||||
txtPassword.clear();
|
||||
txtPassword.focus = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: loginModel
|
||||
onLoginResponseChanged: {
|
||||
const loginResponse = JSON.parse(response);
|
||||
if(loginResponse.error){
|
||||
loginError.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Finish"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
onClicked: {
|
||||
const selectedAccountIndex = wizardStep2.selectedIndex
|
||||
const response = loginModel.login(selectedAccountIndex, txtPassword.text)
|
||||
// TODO: replace me with something graphical (ie spinner)
|
||||
console.log("Logging in...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;autoSize:true;height:480;width:640}
|
||||
}
|
||||
##^##*/
|
|
@ -9,7 +9,7 @@ Page {
|
|||
|
||||
DSM.StateMachine {
|
||||
id: stateMachine
|
||||
initialState: stateIntro
|
||||
initialState: showLogin ? stateLogin : stateIntro
|
||||
running: onboardingMain.visible
|
||||
|
||||
DSM.State {
|
||||
|
@ -43,25 +43,36 @@ Page {
|
|||
id: existingKeyState
|
||||
onEntered: existingKey.visible = true
|
||||
onExited: existingKey.visible = false
|
||||
|
||||
// DSM.SignalTransition {
|
||||
// targetState: keysMainState
|
||||
// signal: keysMain.btnExistingKey.clicked
|
||||
// }
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: genKeyState
|
||||
onEntered: {
|
||||
genKey.visible = true
|
||||
}
|
||||
onEntered: genKey.visible = true
|
||||
onExited: genKey.visible = false
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: appState
|
||||
signal: genKey.loginDone
|
||||
// guard: !response.error
|
||||
}
|
||||
DSM.SignalTransition {
|
||||
targetState: appState
|
||||
signal: onboardingModel.loginResponseChanged
|
||||
guard: {
|
||||
const resp = JSON.parse(response);
|
||||
return !resp.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DSM.State {
|
||||
id: stateLogin
|
||||
onEntered: login.visible = true
|
||||
onExited: login.visible = false
|
||||
|
||||
DSM.SignalTransition {
|
||||
targetState: appState
|
||||
signal: loginModel.loginResponseChanged
|
||||
guard: {
|
||||
const resp = JSON.parse(response);
|
||||
return !resp.error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DSM.FinalState {
|
||||
|
@ -74,7 +85,7 @@ Page {
|
|||
Intro {
|
||||
id: intro
|
||||
anchors.fill: parent
|
||||
visible: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
KeysMain {
|
||||
|
@ -94,6 +105,12 @@ Page {
|
|||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
|
||||
Login {
|
||||
id: login
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d0f5be4971ad34d115b9749d9fb69bdd2aecf525
|
||||
Subproject commit a99dafab420bcbbffee35e9bd847a9014eafaffe
|
Loading…
Reference in New Issue