feat(@desktop/keycard): initial keycard implementation

Keycard implementation affected onboarding/login flows.
- new user - first run - new keys into keycard
- new user - first run - import seed phrase into keycard
- old user - first run - login importing from keycard
- login the app using keycard

Fixes: #5972
This commit is contained in:
Sale Djenic 2022-07-21 13:29:18 +02:00 committed by saledjenic
parent 03f1fe500b
commit db44bc25d3
109 changed files with 4677 additions and 921 deletions

3
.gitmodules vendored
View File

@ -118,3 +118,6 @@
[submodule "vendor/nim-keycard-go"]
path = vendor/nim-keycard-go
url = https://github.com/status-im/nim-keycard-go
[submodule "vendor/status-keycard-go"]
path = vendor/status-keycard-go
url = https://github.com/status-im/status-keycard-go.git

View File

@ -35,7 +35,7 @@ BUILD_SYSTEM_DIR := vendor/nimbus-build-system
run-macos \
run-windows \
status-go \
keycard-go \
status-keycard-go \
update
ifeq ($(NIM_PARAMS),)
@ -223,15 +223,15 @@ $(STATUSGO): | deps
$(MAKE) statusgo-shared-library $(HANDLE_OUTPUT)
KEYCARDGO := vendor/nim-keycard-go/go/keycard/build/libkeycard/libkeycard.$(LIBSTATUS_EXT)
KEYCARDGO_LIBDIR := $(shell pwd)/$(shell dirname "$(KEYCARDGO)")
export KEYCARDGO_LIBDIR
STATUSKEYCARDGO := vendor/status-keycard-go/build/libkeycard/libkeycard.$(LIBSTATUS_EXT)
STATUSKEYCARDGO_LIBDIR := $(shell pwd)/$(shell dirname "$(STATUSKEYCARDGO)")
export STATUSKEYCARDGO_LIBDIR
keycard-go: $(KEYCARDGO)
$(KEYCARDGO): | deps
echo -e $(BUILD_MSG) "keycard-go"
+ cd vendor/nim-keycard-go && \
$(MAKE) build-keycard-go $(HANDLE_OUTPUT)
status-keycard-go: $(STATUSKEYCARDGO)
$(STATUSKEYCARDGO): | deps
echo -e $(BUILD_MSG) "status-keycard-go"
+ cd vendor/status-keycard-go && \
$(MAKE) build-lib $(HANDLE_OUTPUT)
QRCODEGEN := vendor/QR-Code-generator/c/libqrcodegen.a
@ -331,9 +331,9 @@ else
endif
$(NIM_STATUS_CLIENT): NIM_PARAMS += $(RESOURCES_LAYOUT)
$(NIM_STATUS_CLIENT): $(NIM_SOURCES) | $(DOTHERSIDE) $(STATUSGO) $(KEYCARDGO) $(QRCODEGEN) $(FLEETS) rcc $(QM_BINARIES) deps
$(NIM_STATUS_CLIENT): $(NIM_SOURCES) | $(DOTHERSIDE) $(STATUSGO) $(STATUSKEYCARDGO) $(QRCODEGEN) $(FLEETS) rcc $(QM_BINARIES) deps
echo -e $(BUILD_MSG) "$@" && \
$(ENV_SCRIPT) nim c $(NIM_PARAMS) --passL:"-L$(STATUSGO_LIBDIR)" --passL:"-lstatus" --passL:"-L$(KEYCARDGO_LIBDIR)" --passL:"-lkeycard" $(NIM_EXTRA_PARAMS) --passL:"$(QRCODEGEN)" --passL:"-lm" src/nim_status_client.nim && \
$(ENV_SCRIPT) nim c $(NIM_PARAMS) --passL:"-L$(STATUSGO_LIBDIR)" --passL:"-lstatus" --passL:"-L$(STATUSKEYCARDGO_LIBDIR)" --passL:"-lkeycard" $(NIM_EXTRA_PARAMS) --passL:"$(QRCODEGEN)" --passL:"-lm" src/nim_status_client.nim && \
[[ $$? = 0 ]] && \
(([[ $(detected_OS) = Darwin ]] && \
install_name_tool -change \
@ -407,7 +407,7 @@ $(STATUS_CLIENT_APPIMAGE): nim_status_client $(APPIMAGE_TOOL) nim-status.desktop
cp -r /usr/lib/x86_64-linux-gnu/gstreamer1.0 tmp/linux/dist/usr/lib/
cp vendor/status-go/build/bin/libstatus.so tmp/linux/dist/usr/lib/
cp vendor/status-go/build/bin/libstatus.so.0 tmp/linux/dist/usr/lib/
cp $(KEYCARDGO) tmp/linux/dist/usr/lib/
cp $(STATUSKEYCARDGO) tmp/linux/dist/usr/lib/
echo -e $(BUILD_MSG) "AppImage"
linuxdeployqt tmp/linux/dist/nim-status.desktop -no-copy-copyright-files -qmldir=ui -qmlimport=$(QTDIR)/qml -bundle-non-qt-libs
@ -530,7 +530,7 @@ $(STATUS_CLIENT_EXE): nim_status_client nim_windows_launcher $(NIM_WINDOWS_PREBU
cp bin/nim_windows_launcher.exe $(OUTPUT)/Status.exe
rcedit $(OUTPUT)/bin/Status.exe --set-icon $(OUTPUT)/resources/status.ico
rcedit $(OUTPUT)/Status.exe --set-icon $(OUTPUT)/resources/status.ico
cp $(DOTHERSIDE) $(STATUSGO) $(KEYCARDGO) tmp/windows/tools/*.dll $(OUTPUT)/bin/
cp $(DOTHERSIDE) $(STATUSGO) $(STATUSKEYCARDGO) tmp/windows/tools/*.dll $(OUTPUT)/bin/
cp "$(shell which libgcc_s_seh-1.dll)" $(OUTPUT)/bin/
cp "$(shell which libwinpthread-1.dll)" $(OUTPUT)/bin/
echo -e $(BUILD_MSG) "deployable folder"
@ -575,7 +575,7 @@ pkg-windows: check-pkg-target-windows $(STATUS_CLIENT_EXE)
zip-windows: check-pkg-target-windows $(STATUS_CLIENT_7Z)
clean: | clean-common
rm -rf bin/* node_modules bottles/* pkg/* tmp/* $(STATUSGO) $(KEYCARDGO)
rm -rf bin/* node_modules bottles/* pkg/* tmp/* $(STATUSGO) $(STATUSKEYCARDGO)
+ $(MAKE) -C vendor/DOtherSide/build --no-print-directory clean
force-rebuild-status-go:
@ -594,7 +594,7 @@ $(ICON_TOOL):
run-linux: nim_status_client
echo -e "\e[92mRunning:\e[39m bin/nim_status_client"
LD_LIBRARY_PATH="$(QT5_LIBDIR)":"$(STATUSGO_LIBDIR)":"$(KEYCARDGO_LIBDIR)" \
LD_LIBRARY_PATH="$(QT5_LIBDIR)":"$(STATUSGO_LIBDIR)":"$(STATUSKEYCARDGO_LIBDIR)" \
./bin/nim_status_client
run-macos: nim_status_client $(ICON_TOOL)
@ -609,7 +609,7 @@ run-macos: nim_status_client $(ICON_TOOL)
run-windows: nim_status_client $(NIM_WINDOWS_PREBUILT_DLLS)
echo -e "\e[92mRunning:\e[39m bin/nim_status_client.exe"
PATH="$(shell pwd)"/"$(shell dirname "$(DOTHERSIDE)")":"$(STATUSGO_LIBDIR)":"$(KEYCARDGO_LIBDIR)":"$(shell pwd)"/"$(shell dirname "$(NIM_WINDOWS_PREBUILT_DLLS)")":"$(PATH)" \
PATH="$(shell pwd)"/"$(shell dirname "$(DOTHERSIDE)")":"$(STATUSGO_LIBDIR)":"$(STATUSKEYCARDGO_LIBDIR)":"$(shell pwd)"/"$(shell dirname "$(NIM_WINDOWS_PREBUILT_DLLS)")":"$(PATH)" \
./bin/nim_status_client.exe
endif # "variables.mk" was not included

View File

@ -16,7 +16,7 @@ if defined(macosx):
# note: macdeployqt rewrites rpath appropriately when building the .app bundle
switch("passL", "-rpath" & " " & getEnv("QT5_LIBDIR"))
switch("passL", "-rpath" & " " & getEnv("STATUSGO_LIBDIR"))
switch("passL", "-rpath" & " " & getEnv("KEYCARDGO_LIBDIR"))
switch("passL", "-rpath" & " " & getEnv("STATUSKEYCARDGO_LIBDIR"))
# statically link these libs
switch("passL", "bottles/openssl@1.1/lib/libcrypto.a")
switch("passL", "bottles/openssl@1.1/lib/libssl.a")

View File

@ -2,6 +2,7 @@ import NimQml, chronicles
import ../../app_service/service/general/service as general_service
import ../../app_service/service/keychain/service as keychain_service
import ../../app_service/service/keycard/service as keycard_service
import ../../app_service/service/accounts/service as accounts_service
import ../../app_service/service/contacts/service as contacts_service
import ../../app_service/service/language/service as language_service
@ -56,6 +57,7 @@ type
# Services
generalService: general_service.Service
keycardService*: keycard_service.Service
keychainService: keychain_service.Service
accountsService: accounts_service.Service
contactsService: contacts_service.Service
@ -127,6 +129,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
# Services
result.generalService = general_service.newService()
result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService)
result.keychainService = keychain_service.newService(statusFoundation.events)
@ -189,7 +192,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.keychainService,
result.accountsService,
result.generalService,
result.profileService
result.profileService,
result.keycardService
)
result.mainModule = main_module.newModule[AppController](
result,
@ -272,6 +276,7 @@ proc delete*(self: AppController) =
self.generalService.delete
self.ensService.delete
self.gifService.delete
self.keycardService.delete
proc startupDidLoad*(self: AppController) =
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
@ -290,6 +295,7 @@ proc mainDidLoad*(self: AppController) =
self.mainModule.checkForStoringPassword()
proc start*(self: AppController) =
self.keycardService.init()
self.keychainService.init()
self.generalService.init()
self.accountsService.init()

View File

@ -5,8 +5,6 @@ import ../../constants
# Local Account Settings keys:
const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain"
const DEFAULT_STORE_TO_KEYCHAIN = "notNow"
const LS_KEY_IS_KEYCARD_ENABLED* = "isKeycardEnabled"
const DEFAULT_IS_KEYCARD_ENABLED = false
# Local Account Settings values:
const LS_VALUE_STORE* = "store"
const LS_VALUE_NOTNOW* = "notNow"
@ -35,23 +33,17 @@ QtObject:
proc setFileName*(self: LocalAccountSettings, fileName: string) =
if(not self.settings.isNil):
self.settings.delete
let filePath = os.joinPath(self.settingsFileDir, fileName)
self.settings = newQSettings(filePath, QSettingsFormat.IniFormat)
proc storeToKeychainValueChanged*(self: LocalAccountSettings) {.signal.}
proc isKeycardEnabledChanged*(self: LocalAccountSettings) {.signal.}
proc removeKey*(self: LocalAccountSettings, key: string) =
if(self.settings.isNil):
return
self.settings.remove(key)
if(key == LS_KEY_STORE_TO_KEYCHAIN):
self.storeToKeychainValueChanged()
elif(key == LS_KEY_IS_KEYCARD_ENABLED):
self.isKeycardEnabledChanged()
proc getStoreToKeychainValue*(self: LocalAccountSettings): string {.slot.} =
if(self.settings.isNil):
@ -70,22 +62,3 @@ QtObject:
read = getStoreToKeychainValue
write = setStoreToKeychainValue
notify = storeToKeychainValueChanged
proc getIsKeycardEnabled*(self: LocalAccountSettings): bool {.slot.} =
if(self.settings.isNil):
return DEFAULT_IS_KEYCARD_ENABLED
self.settings.value(LS_KEY_IS_KEYCARD_ENABLED).boolVal
proc setIsKeycardEnabled*(self: LocalAccountSettings, value: bool) {.slot.} =
if(self.settings.isNil):
return
self.settings.setValue(LS_KEY_IS_KEYCARD_ENABLED, newQVariant(value))
self.isKeycardEnabledChanged()
QtProperty[bool] isKeycardEnabled:
read = getIsKeycardEnabled
write = setIsKeycardEnabled
notify = isKeycardEnabledChanged

View File

@ -91,6 +91,8 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e)
# with the following condition we guard unintentional props deletion from the `.ini` file
if self.accountsService.getLoggedInAccount().isValid():
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitStoringPasswordError(args.errDescription)
@ -253,7 +255,7 @@ proc storePassword*(self: Controller, password: string) =
if (value != LS_VALUE_STORE or account.name.len == 0):
return
self.keychainService.storePassword(account.name, password)
self.keychainService.storeData(account.name, password)
proc getActiveSectionId*(self: Controller): string =
result = self.activeSectionId

View File

@ -1,4 +1,4 @@
import chronicles
import chronicles, strutils, os
import io_interface
@ -9,12 +9,14 @@ import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/accounts/service as accounts_service
import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/profile/service as profile_service
import ../../../app_service/service/keycard/service as keycard_service
logScope:
topics = "startup-controller"
type ProfileImageDetails = object
url*: string
croppedImage*: string
x1*: int
y1*: int
x2*: int
@ -28,18 +30,29 @@ type
accountsService: accounts_service.Service
keychainService: keychain_service.Service
profileService: profile_service.Service
keycardService: keycard_service.Service
tmpProfileImageDetails: ProfileImageDetails
tmpDisplayName: string
tmpPassword: string
tmpMnemonic: string
tmpSelectedLoginAccountKeyUid: string
tmpPin: string
tmpPinMatch: bool
tmpPuk: string
tmpValidPuk: bool
tmpSeedPhrase: string
tmpSeedPhraseLength: int
tmpKeyUid: string
tmpKeycardEvent: KeycardEvent
tmpKeychainErrorOccurred: bool
tmpRecoverUsingSeedPhraseWhileLogin: bool
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
generalService: general_service.Service,
accountsService: accounts_service.Service,
keychainService: keychain_service.Service,
profileService: profile_service.Service):
profileService: profile_service.Service,
keycardService: keycard_service.Service):
Controller =
result = Controller()
result.delegate = delegate
@ -48,6 +61,14 @@ proc newController*(delegate: io_interface.AccessInterface,
result.accountsService = accountsService
result.keychainService = keychainService
result.profileService = profileService
result.keycardService = keycardService
result.tmpPinMatch = false
result.tmpSeedPhraseLength = 0
result.tmpKeychainErrorOccurred = true
result.tmpRecoverUsingSeedPhraseWhileLogin = false
# Forward declaration
proc cleanTmpData(self: Controller)
proc delete*(self: Controller) =
discard
@ -56,10 +77,12 @@ proc init*(self: Controller) =
self.events.on(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e)
self.delegate.onNodeLogin(signal.event.error)
self.cleanTmpData()
self.events.on(SignalType.NodeStopped.event) do(e:Args):
self.events.emit("nodeStopped", Args())
self.accountsService.clear()
self.cleanTmpData()
self.delegate.emitLogOut()
self.events.on(SignalType.NodeReady.event) do(e:Args):
@ -71,11 +94,12 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e)
# We are notifying user only about keychain errors.
if (args.errType == ERROR_TYPE_AUTHENTICATION):
return
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitObtainingPasswordError(args.errDescription)
self.tmpKeychainErrorOccurred = true
self.delegate.emitObtainingPasswordError(args.errDescription, args.errType)
self.events.on(SignalKeycardResponse) do(e: Args):
let args = KeycardArgs(e)
self.delegate.onKeycardResponse(args.flowType, args.flowEvent)
proc shouldStartWithOnboardingScreen*(self: Controller): bool =
return self.accountsService.openedAccounts().len == 0
@ -86,22 +110,22 @@ proc getGeneratedAccounts*(self: Controller): seq[GeneratedAccountDto] =
proc getImportedAccount*(self: Controller): GeneratedAccountDto =
return self.accountsService.getImportedAccount()
proc generateImages*(self: Controller, image: string, aX: int, aY: int, bX: int, bY: int): seq[general_service.Image] =
return self.generalService.generateImages(image, aX, aY, bX, bY)
proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)
proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
let formatedImg = singletonInstance.utils.formatImagePath(imageUrl)
let images = self.generateImages(formatedImg, aX, aY, bX, bY)
let images = self.generalService.generateImages(formatedImg, aX, aY, bX, bY)
if(images.len == 0):
return
for img in images:
if(img.imgType == "large"):
self.tmpProfileImageDetails = ProfileImageDetails(url: imageUrl, x1: aX, y1: aY, x2: bX, y2: bY)
self.tmpProfileImageDetails = ProfileImageDetails(url: imageUrl, croppedImage: img.uri, x1: aX, y1: aY, x2: bX, y2: bY)
return img.uri
proc getCroppedProfileImage*(self: Controller): string =
return self.tmpProfileImageDetails.croppedImage
proc setDisplayName*(self: Controller, value: string) =
self.tmpDisplayName = value
@ -114,12 +138,83 @@ proc setPassword*(self: Controller, value: string) =
proc getPassword*(self: Controller): string =
return self.tmpPassword
proc setPin*(self: Controller, value: string) =
self.tmpPin = value
proc getPin*(self: Controller): string =
return self.tmpPin
proc setPinMatch*(self: Controller, value: bool) =
self.tmpPinMatch = value
proc getPinMatch*(self: Controller): bool =
return self.tmpPinMatch
proc setPuk*(self: Controller, value: string) =
self.tmpPuk = value
proc getPuk*(self: Controller): string =
return self.tmpPuk
proc setPukValid*(self: Controller, value: bool) =
self.tmpValidPuk = value
proc getValidPuk*(self: Controller): bool =
return self.tmpValidPuk
proc setSeedPhrase*(self: Controller, value: string) =
let words = value.split(" ")
self.tmpSeedPhrase = value
self.tmpSeedPhraseLength = words.len
proc getSeedPhrase*(self: Controller): string =
return self.tmpSeedPhrase
proc getSeedPhraseLength*(self: Controller): int =
return self.tmpSeedPhraseLength
proc setKeyUid*(self: Controller, value: string) =
self.tmpKeyUid = value
proc setKeycardData*(self: Controller, value: string) =
self.delegate.setKeycardData(value)
proc setKeycardEvent*(self: Controller, value: KeycardEvent) =
self.tmpKeycardEvent = value
proc keychainErrorOccurred*(self: Controller): bool =
return self.tmpKeychainErrorOccurred
proc setRecoverUsingSeedPhraseWhileLogin*(self: Controller, value: bool) =
self.tmpRecoverUsingSeedPhraseWhileLogin = value
proc getRecoverUsingSeedPhraseWhileLogin*(self: Controller): bool =
return self.tmpRecoverUsingSeedPhraseWhileLogin
proc cleanTmpData(self: Controller) =
self.tmpSelectedLoginAccountKeyUid = ""
self.tmpProfileImageDetails = ProfileImageDetails()
self.tmpKeychainErrorOccurred = true
self.setDisplayName("")
self.setPassword("")
self.setPin("")
self.setPinMatch(false)
self.setPuk("")
self.setPukValid(false)
self.setSeedPhrase("")
self.setKeyUid("")
self.setKeycardEvent(KeycardEvent())
self.setRecoverUsingSeedPhraseWhileLogin(false)
proc storePasswordToKeychain(self: Controller) =
let account = self.accountsService.getLoggedInAccount()
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE or account.name.len == 0):
return
self.keychainService.storePassword(account.name, self.tmpPassword)
#TODO: we should store PubKey of this account instead of display name (display name is not unique)
# and we may run into a problem if 2 accounts with the same display name are generated.
let data = if self.tmpPassword.len > 0: self.tmpPassword else: self.tmpPin
self.keychainService.storeData(account.name, data)
proc storeIdentityImage*(self: Controller) =
if self.tmpProfileImageDetails.url.len == 0:
@ -130,8 +225,24 @@ proc storeIdentityImage*(self: Controller) =
self.tmpProfileImageDetails.y1, self.tmpProfileImageDetails.x2, self.tmpProfileImageDetails.y2)
self.tmpProfileImageDetails = ProfileImageDetails()
proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool) =
let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName)
proc validMnemonic*(self: Controller, mnemonic: string): bool =
let err = self.accountsService.validateMnemonic(mnemonic)
if err.len == 0:
self.setSeedPhrase(mnemonic)
return true
return false
proc importMnemonic*(self: Controller): bool =
let error = self.accountsService.importMnemonic(self.tmpSeedPhrase)
if(error.len == 0):
self.delegate.importAccountSuccess()
return true
else:
self.delegate.importAccountError(error)
return false
proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool, keycardUsage: bool) =
let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName, keycardUsage)
if error != "":
self.delegate.setupAccountError(error)
else:
@ -140,8 +251,6 @@ proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool) =
self.storePasswordToKeychain()
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
self.setPassword("")
self.setDisplayName("")
proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accounts = self.getGeneratedAccounts()
@ -149,27 +258,30 @@ proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
error "list of generated accounts is empty"
return
let accountId = accounts[0].id
self.setupAccount(accountId, storeToKeychain)
self.setupAccount(accountId, storeToKeychain, keycardUsage = false)
proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accountId = self.getImportedAccount().id
self.setupAccount(accountId, storeToKeychain)
self.setupAccount(accountId, storeToKeychain, keycardUsage = false)
proc validMnemonic*(self: Controller, mnemonic: string): bool =
let err = self.accountsService.validateMnemonic(mnemonic)
if err.len == 0:
self.tmpMnemonic = mnemonic
return true
return false
proc importMnemonic*(self: Controller): bool =
let error = self.accountsService.importMnemonic(self.tmpMnemonic)
if(error.len == 0):
self.delegate.importAccountSuccess()
return true
proc storeKeycardAccountAndLogin*(self: Controller, storeToKeychain: bool) =
if self.importMnemonic():
let accountId = self.getImportedAccount().id
self.setupAccount(accountId, storeToKeychain, keycardUsage = true)
else:
self.delegate.importAccountError(error)
return false
error "an error ocurred while importing mnemonic"
proc setupKeycardAccount*(self: Controller, storeToKeychain: bool) =
if self.tmpSeedPhrase.len > 0:
# if `tmpSeedPhrase` is not empty means user has recovered keycard via seed phrase
self.storeKeycardAccountAndLogin(storeToKeychain)
else:
self.accountsService.setupAccountKeycard(self.tmpKeycardEvent)
if storeToKeychain:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_STORE)
self.storePasswordToKeychain()
else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
return self.accountsService.openedAccounts()
@ -180,21 +292,95 @@ proc getSelectedLoginAccount(self: Controller): AccountDto =
if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid):
return acc
proc keyUidMatch*(self: Controller, keyUid: string): bool =
return self.tmpSelectedLoginAccountKeyUid == keyUid
proc setSelectedLoginAccountKeyUid*(self: Controller, keyUid: string) =
self.tmpSelectedLoginAccountKeyUid = keyUid
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
proc isKeycardCreatedAccountSelectedOne*(self: Controller): bool =
let selectedAccount = self.getSelectedLoginAccount()
return selectedAccount.keycardPairing.len > 0
proc tryToObtainDataFromKeychain*(self: Controller) =
# Dealing with Keychain is the MacOS only feature
if(not defined(macosx)):
return
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE):
return
self.keychainService.tryToObtainPassword(selectedAccount.name)
self.tmpKeychainErrorOccurred = false
let selectedAccount = self.getSelectedLoginAccount()
self.keychainService.tryToObtainData(selectedAccount.name)
proc login*(self: Controller) =
let selectedAccount = self.getSelectedLoginAccount()
let error = self.accountsService.login(selectedAccount, self.tmpPassword)
self.setPassword("")
if(error.len > 0):
self.delegate.emitAccountLoginError(error)
proc loginAccountKeycard*(self: Controller) =
let error = self.accountsService.loginAccountKeycard(self.tmpKeycardEvent)
if(error.len > 0):
self.delegate.emitAccountLoginError(error)
proc cancelCurrentFlow(self: Controller) =
self.keycardService.cancelCurrentFlow()
# in most cases we're running another flow after canceling the current one,
# this way we're giving to the keycard some time to cancel the current flow
sleep(200)
proc runLoadAccountFlow*(self: Controller, factoryReset = false) =
self.cancelCurrentFlow() # before running into any flow we're making sure that the previous flow is canceled
self.keycardService.startLoadAccountFlow(factoryReset)
proc runLoadAccountFlowWithSeedPhrase*(self: Controller, seedPhraseLength: int, seedPhrase: string, factoryReset = false) =
self.cancelCurrentFlow() # before running into any flow we're making sure that the previous flow is canceled
self.keycardService.startLoadAccountFlowWithSeedPhrase(seedPhraseLength, seedPhrase, factoryReset)
proc runLoginFlow*(self: Controller) =
self.cancelCurrentFlow() # before running into any flow we're making sure that the previous flow is canceled
self.keycardService.startLoginFlow()
proc startLoginFlowAutomatically*(self: Controller, pin: string) =
self.cancelCurrentFlow() # before running into any flow we're making sure that the previous flow is canceled
self.keycardService.startLoginFlowAutomatically(pin)
proc runRecoverAccountFlow*(self: Controller) =
self.cancelCurrentFlow() # before running into any flow we're making sure that the previous flow is canceled
self.keycardService.startRecoverAccountFlow()
proc resumeCurrentFlow*(self: Controller) =
self.keycardService.resumeCurrentFlow()
proc resumeCurrentFlowLater*(self: Controller) =
self.keycardService.resumeCurrentFlowLater()
proc factoryReset*(self: Controller) =
self.keycardService.factoryReset()
proc storePinToKeycard*(self: Controller, pin: string, puk: string) =
self.keycardService.storePin(pin, puk)
proc enterKeycardPin*(self: Controller, pin: string) =
self.keycardService.enterPin(pin)
proc enterKeycardPuk*(self: Controller, puk: string) =
self.keycardService.enterPuk(puk)
proc storeSeedPhraseToKeycard*(self: Controller, seedPhraseLength: int, seedPhrase: string) =
self.keycardService.storeSeedPhrase(seedPhraseLength, seedPhrase)
proc buildSeedPhrasesFromIndexes*(self: Controller, seedPhraseIndexes: seq[int]) =
if seedPhraseIndexes.len == 0:
let err = "cannot generate mnemonic"
error "keycard error: ", err
## TODO: we should not be ever in this block, but maybe we can cancel flow and reset states (as the app was just strated)
return
let sp = self.keycardService.buildSeedPhrasesFromIndexes(seedPhraseIndexes)
self.setSeedPhrase(sp.join(" "))
proc generateRandomPUK*(self: Controller): string =
return self.keycardService.generateRandomPUK()

View File

@ -1,6 +1,3 @@
import state
import ../controller
type
BiometricsState* = ref object of State
@ -11,28 +8,36 @@ proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState
proc delete*(self: BiometricsState) =
self.State.delete
method moveToNextPrimaryState*(self: BiometricsState): bool =
return false
method moveToNextSecondaryState*(self: BiometricsState): bool =
return false
method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = true # true, cause we have support for keychain for mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
controller.storeGeneratedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = true) # true, cause we have support for keychain for mac os
## This should not be the correct call for this flow, this is an issue,
## but since current implementation is like that and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)
method executeSecondaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
controller.storeGeneratedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
## This should not be the correct call for this flow, this is an issue,
## but since current implementation is like that and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeKeycardAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.setupKeycardAccount(storeToKeychain)

View File

@ -0,0 +1,23 @@
type
KeycardCreatePinState* = ref object of State
pinValid: bool
proc newKeycardCreatePinState*(flowType: FlowType, backState: State): KeycardCreatePinState =
result = KeycardCreatePinState()
result.setup(flowType, StateType.KeycardCreatePin, backState)
result.pinValid = false
proc delete*(self: KeycardCreatePinState) =
self.State.delete
method executeBackCommand*(self: KeycardCreatePinState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method getNextPrimaryState*(self: KeycardCreatePinState, controller: Controller): State =
if not self.pinValid:
return nil
return createState(StateType.KeycardRepeatPin, self.flowType, self.getBackState)
method executePrimaryCommand*(self: KeycardCreatePinState, controller: Controller) =
self.pinValid = controller.getPin().len == PINLengthForStatusApp

View File

@ -0,0 +1,16 @@
type
KeycardDisplaySeedPhraseState* = ref object of State
proc newKeycardDisplaySeedPhraseState*(flowType: FlowType, backState: State): KeycardDisplaySeedPhraseState =
result = KeycardDisplaySeedPhraseState()
result.setup(flowType, StateType.KeycardDisplaySeedPhrase, backState)
proc delete*(self: KeycardDisplaySeedPhraseState) =
self.State.delete
method executeBackCommand*(self: KeycardDisplaySeedPhraseState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method getNextPrimaryState*(self: KeycardDisplaySeedPhraseState, controller: Controller): State =
return createState(StateType.KeycardEnterSeedPhraseWords, self.flowType, self.getBackState)

View File

@ -0,0 +1,24 @@
type
KeycardEmptyState* = ref object of State
proc newKeycardEmptyState*(flowType: FlowType, backState: State): KeycardEmptyState =
result = KeycardEmptyState()
result.setup(flowType, StateType.KeycardEmpty, backState)
proc delete*(self: KeycardEmptyState) =
self.State.delete
method executePrimaryCommand*(self: KeycardEmptyState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.runLoadAccountFlow(true)
method resolveKeycardNextState*(self: KeycardEmptyState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserNewKeycardKeys, self)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, FlowType.FirstRunNewUserNewKeycardKeys, self)

View File

@ -0,0 +1,45 @@
type
KeycardEnterPinState* = ref object of State
pinValid: bool
proc newKeycardEnterPinState*(flowType: FlowType, backState: State): KeycardEnterPinState =
result = KeycardEnterPinState()
result.setup(flowType, StateType.KeycardEnterPin, backState)
result.pinValid = false
proc delete*(self: KeycardEnterPinState) =
self.State.delete
method getNextPrimaryState*(self: KeycardEnterPinState, controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
return createState(StateType.KeycardDisplaySeedPhrase, self.flowType, self.getBackState)
elif self.flowType == FirstRunNewUserImportSeedPhraseIntoKeycard:
return createState(StateType.UserProfileCreate, self.flowType, self.getBackState)
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
return nil
method executeBackCommand*(self: KeycardEnterPinState, controller: Controller) =
controller.setPin("")
method executePrimaryCommand*(self: KeycardEnterPinState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
self.pinValid = controller.getPin().len == PINLengthForStatusApp
if self.pinValid:
controller.enterKeycardPin(controller.getPin())
method resolveKeycardNextState*(self: KeycardEnterPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return createState(StateType.KeycardWrongPin, self.flowType, self.getBackState)
return createState(StateType.KeycardMaxPinRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
if not defined(macosx):
controller.setupKeycardAccount(false)
return nil
return createState(StateType.Biometrics, self.flowType, self)

View File

@ -0,0 +1,44 @@
type
KeycardEnterPukState* = ref object of State
proc newKeycardEnterPukState*(flowType: FlowType, backState: State): KeycardEnterPukState =
result = KeycardEnterPukState()
result.setup(flowType, StateType.KeycardEnterPuk, backState)
proc delete*(self: KeycardEnterPukState) =
self.State.delete
method executePrimaryCommand*(self: KeycardEnterPukState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if controller.getPuk().len == PUKLengthForStatusApp:
controller.enterKeycardPuk(controller.getPuk())
elif self.flowType == FlowType.AppLogin:
if controller.getPuk().len == PUKLengthForStatusApp:
controller.enterKeycardPuk(controller.getPuk())
method resolveKeycardNextState*(self: KeycardEnterPukState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorUnblocking:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
if keycardEvent.pukRetries > 0:
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorUnblocking:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
if keycardEvent.pukRetries > 0:
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)

View File

@ -0,0 +1,24 @@
type
KeycardEnterSeedPhraseWordsState* = ref object of State
proc newKeycardEnterSeedPhraseWordsState*(flowType: FlowType, backState: State): KeycardEnterSeedPhraseWordsState =
result = KeycardEnterSeedPhraseWordsState()
result.setup(flowType, StateType.KeycardEnterSeedPhraseWords, backState)
proc delete*(self: KeycardEnterSeedPhraseWordsState) =
self.State.delete
method executeBackCommand*(self: KeycardEnterSeedPhraseWordsState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method executePrimaryCommand*(self: KeycardEnterSeedPhraseWordsState, controller: Controller) =
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
method resolveKeycardNextState*(self: KeycardEnterSeedPhraseWordsState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
controller.setKeyUid(keycardEvent.keyUid)
return createState(StateType.UserProfileCreate, self.flowType, self)

View File

@ -0,0 +1,15 @@
type
KeycardInsertKeycardState* = ref object of State
proc newKeycardInsertKeycardState*(flowType: FlowType, backState: State): KeycardInsertKeycardState =
result = KeycardInsertKeycardState()
result.setup(flowType, StateType.KeycardInsertKeycard, backState)
proc delete*(self: KeycardInsertKeycardState) =
self.State.delete
method resolveKeycardNextState*(self: KeycardInsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if keycardFlowType == ResponseTypeValueCardInserted:
return createState(StateType.KeycardReadingKeycard, self.flowType, self.getBackState)
return nil

View File

@ -0,0 +1,12 @@
type
KeycardLockedState* = ref object of State
proc newKeycardLockedState*(flowType: FlowType, backState: State): KeycardLockedState =
result = KeycardLockedState()
result.setup(flowType, StateType.KeycardLocked, backState)
proc delete*(self: KeycardLockedState) =
self.State.delete
method getNextPrimaryState*(self: KeycardLockedState, controller: Controller): State =
return createState(StateType.KeycardEnterSeedPhraseWords, self.flowType, self)

View File

@ -0,0 +1,28 @@
type
KeycardMaxPairingSlotsReachedState* = ref object of State
proc newKeycardMaxPairingSlotsReachedState*(flowType: FlowType, backState: State): KeycardMaxPairingSlotsReachedState =
result = KeycardMaxPairingSlotsReachedState()
result.setup(flowType, StateType.KeycardMaxPairingSlotsReached, backState)
proc delete*(self: KeycardMaxPairingSlotsReachedState) =
self.State.delete
method executePrimaryCommand*(self: KeycardMaxPairingSlotsReachedState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.runLoadAccountFlow(true)
method executeSecondaryCommand*(self: KeycardMaxPairingSlotsReachedState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.resumeCurrentFlow()
method resolveKeycardNextState*(self: KeycardMaxPairingSlotsReachedState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard, self.getBackState)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard, self.getBackState)

View File

@ -0,0 +1,12 @@
type
KeycardMaxPinRetriesReachedState* = ref object of State
proc newKeycardMaxPinRetriesReachedState*(flowType: FlowType, backState: State): KeycardMaxPinRetriesReachedState =
result = KeycardMaxPinRetriesReachedState()
result.setup(flowType, StateType.KeycardMaxPinRetriesReached, backState)
proc delete*(self: KeycardMaxPinRetriesReachedState) =
self.State.delete
method getNextPrimaryState*(self: KeycardMaxPinRetriesReachedState, controller: Controller): State =
return createState(StateType.KeycardRecover, self.flowType(), self.getBackState)

View File

@ -0,0 +1,40 @@
type
KeycardMaxPukRetriesReachedState* = ref object of State
proc newKeycardMaxPukRetriesReachedState*(flowType: FlowType, backState: State): KeycardMaxPukRetriesReachedState =
result = KeycardMaxPukRetriesReachedState()
result.setup(flowType, StateType.KeycardMaxPukRetriesReached, backState)
proc delete*(self: KeycardMaxPukRetriesReachedState) =
self.State.delete
method executePrimaryCommand*(self: KeycardMaxPukRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.runLoadAccountFlow(true)
elif self.flowType == FlowType.AppLogin:
controller.runLoadAccountFlow(true)
method executeSecondaryCommand*(self: KeycardMaxPukRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.resumeCurrentFlow()
elif self.flowType == FlowType.AppLogin:
controller.resumeCurrentFlow()
method resolveKeycardNextState*(self: KeycardMaxPukRetriesReachedState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard, self.getBackState)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, self.flowType, self.getBackState)

View File

@ -0,0 +1,31 @@
type
KeycardNotEmptyState* = ref object of State
proc newKeycardNotEmptyState*(flowType: FlowType, backState: State): KeycardNotEmptyState =
result = KeycardNotEmptyState()
result.setup(flowType, StateType.KeycardNotEmpty, backState)
proc delete*(self: KeycardNotEmptyState) =
self.State.delete
method executePrimaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.runLoadAccountFlow(true)
method executeSecondaryCommand*(self: KeycardNotEmptyState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.resumeCurrentFlow()
method resolveKeycardNextState*(self: KeycardNotEmptyState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)

View File

@ -0,0 +1,40 @@
type
KeycardPinSetState* = ref object of State
proc newKeycardPinSetState*(flowType: FlowType, backState: State): KeycardPinSetState =
result = KeycardPinSetState()
result.setup(flowType, StateType.KeycardPinSet, backState)
proc delete*(self: KeycardPinSetState) =
self.State.delete
method executeBackCommand*(self: KeycardPinSetState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method getNextPrimaryState*(self: KeycardPinSetState, controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
return createState(StateType.KeycardDisplaySeedPhrase, self.flowType, self.getBackState)
if self.flowType == FirstRunNewUserImportSeedPhraseIntoKeycard:
return createState(StateType.UserProfileCreate, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if controller.getValidPuk():
if not defined(macosx):
return nil
return createState(StateType.Biometrics, self.flowType, self.getBackState)
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if controller.getRecoverUsingSeedPhraseWhileLogin():
return createState(StateType.LoginKeycardReadingKeycard, self.flowType, nil)
if controller.getValidPuk():
controller.loginAccountKeycard()
return nil
return createState(StateType.KeycardWrongPuk, self.flowType, self.getBackState)
method executePrimaryCommand*(self: KeycardPinSetState, controller: Controller) =
if controller.getValidPuk() and not defined(macosx):
controller.setupKeycardAccount(false)
return
if self.flowType == FlowType.AppLogin:
if controller.getRecoverUsingSeedPhraseWhileLogin():
controller.startLoginFlowAutomatically(controller.getPin())

View File

@ -0,0 +1,27 @@
type
KeycardPluginReaderState* = ref object of State
proc newKeycardPluginReaderState*(flowType: FlowType, backState: State): KeycardPluginReaderState =
result = KeycardPluginReaderState()
result.setup(flowType, StateType.KeycardPluginReader, backState)
proc delete*(self: KeycardPluginReaderState) =
self.State.delete
method executePrimaryCommand*(self: KeycardPluginReaderState, controller: Controller) =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.runLoadAccountFlow()
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.runLoadAccountFlow()
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.runRecoverAccountFlow()
method resolveKeycardNextState*(self: KeycardPluginReaderState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return nil
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, self.flowType, self.getBackState)

View File

@ -0,0 +1,72 @@
type
KeycardReadingKeycardState* = ref object of State
proc newKeycardReadingKeycardState*(flowType: FlowType, backState: State): KeycardReadingKeycardState =
result = KeycardReadingKeycardState()
result.setup(flowType, StateType.KeycardReadingKeycard, backState)
proc delete*(self: KeycardReadingKeycardState) =
self.State.delete
method resolveKeycardNextState*(self: KeycardReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.KeycardNotEmpty, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorHasKeys:
return createState(StateType.KeycardNotEmpty, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.KeycardNotEmpty, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorHasKeys:
return createState(StateType.KeycardNotEmpty, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
return createState(StateType.KeycardEnterPin, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.KeycardMaxPinRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorNoKeys:
return createState(StateType.KeycardEmpty, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamFreeSlots:
return createState(StateType.KeycardMaxPairingSlotsReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)

View File

@ -0,0 +1,23 @@
type
KeycardRecoverState* = ref object of State
proc newKeycardRecoverState*(flowType: FlowType, backState: State): KeycardRecoverState =
result = KeycardRecoverState()
result.setup(flowType, StateType.KeycardRecover, backState)
proc delete*(self: KeycardRecoverState) =
self.State.delete
method getNextPrimaryState*(self: KeycardRecoverState, controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self)
if self.flowType == FlowType.AppLogin:
controller.setRecoverUsingSeedPhraseWhileLogin(true)
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, self)
method getNextSecondaryState*(self: KeycardRecoverState, controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.KeycardEnterPuk, self.flowType, self)
if self.flowType == FlowType.AppLogin:
controller.setRecoverUsingSeedPhraseWhileLogin(false)
return createState(StateType.KeycardEnterPuk, self.flowType, self)

View File

@ -0,0 +1,65 @@
type
KeycardRepeatPinState* = ref object of State
proc newKeycardRepeatPinState*(flowType: FlowType, backState: State): KeycardRepeatPinState =
result = KeycardRepeatPinState()
result.setup(flowType, StateType.KeycardRepeatPin, backState)
proc delete*(self: KeycardRepeatPinState) =
self.State.delete
method executeBackCommand*(self: KeycardRepeatPinState, controller: Controller) =
controller.setPin("")
controller.setPinMatch(false)
method executePrimaryCommand*(self: KeycardRepeatPinState, controller: Controller) =
if not controller.getPinMatch():
return
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storePinToKeycard(controller.getPin(), controller.generateRandomPUK())
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.storePinToKeycard(controller.getPin(), puk = "")
elif self.flowType == FlowType.AppLogin:
controller.storePinToKeycard(controller.getPin(), puk = "")
method resolveKeycardNextState*(self: KeycardRepeatPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
if keycardFlowType == ResponseTypeValueEnterMnemonic and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorLoadingKeys:
controller.buildSeedPhrasesFromIndexes(keycardEvent.seedPhraseIndexes)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.keyUid.len > 0:
controller.setKeyUid(keycardEvent.keyUid)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
controller.setPukValid(false)
if keycardEvent.pukRetries > 0:
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
controller.setPukValid(false)
if keycardEvent.pukRetries > 0:
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
return createState(StateType.KeycardPinSet, self.flowType, self.getBackState)

View File

@ -0,0 +1,41 @@
type
KeycardWrongPinState* = ref object of State
pinValid: bool
proc newKeycardWrongPinState*(flowType: FlowType, backState: State): KeycardWrongPinState =
result = KeycardWrongPinState()
result.setup(flowType, StateType.KeycardWrongPin, backState)
result.pinValid = false
proc delete*(self: KeycardWrongPinState) =
self.State.delete
method executeBackCommand*(self: KeycardWrongPinState, controller: Controller) =
controller.setPin("")
method executePrimaryCommand*(self: KeycardWrongPinState, controller: Controller) =
self.pinValid = controller.getPin().len == PINLengthForStatusApp
if self.pinValid:
controller.enterKeycardPin(controller.getPin())
method resolveKeycardNextState*(self: KeycardWrongPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return self
return createState(StateType.KeycardMaxPinRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.KeycardMaxPinRetriesReached, self.flowType, self.getBackState)
return nil
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
if not defined(macosx):
controller.setupKeycardAccount(false)
return nil
return createState(StateType.Biometrics, self.flowType, self)

View File

@ -0,0 +1,58 @@
type
KeycardWrongPukState* = ref object of State
proc newKeycardWrongPukState*(flowType: FlowType, backState: State): KeycardWrongPukState =
result = KeycardWrongPukState()
result.setup(flowType, StateType.KeycardWrongPuk, backState)
proc delete*(self: KeycardWrongPukState) =
self.State.delete
method executePrimaryCommand*(self: KeycardWrongPukState, controller: Controller) =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if controller.getPuk().len == PUKLengthForStatusApp:
controller.enterKeycardPuk(controller.getPuk())
elif self.flowType == FlowType.AppLogin:
if controller.getPuk().len == PUKLengthForStatusApp:
controller.enterKeycardPuk(controller.getPuk())
method resolveKeycardNextState*(self: KeycardWrongPukState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
controller.setPukValid(false)
if keycardEvent.pukRetries > 0:
return nil
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
if not defined(macosx):
controller.setupKeycardAccount(false)
return nil
return createState(StateType.Biometrics, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUK:
controller.setKeycardData($keycardEvent.pukRetries)
controller.setPukValid(false)
if keycardEvent.pukRetries > 0:
return nil
return createState(StateType.KeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.LoginKeycardMaxPukRetriesReached, self.flowType, self.getBackState)
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
controller.setKeycardEvent(keycardEvent)
controller.setPukValid(true)
controller.loginAccountKeycard()
return nil

View File

@ -0,0 +1,30 @@
type
LoginKeycardEmptyState* = ref object of State
proc newLoginKeycardEmptyState*(flowType: FlowType, backState: State): LoginKeycardEmptyState =
result = LoginKeycardEmptyState()
result.setup(flowType, StateType.LoginKeycardEmpty, backState)
proc delete*(self: LoginKeycardEmptyState) =
self.State.delete
method executePrimaryCommand*(self: LoginKeycardEmptyState, controller: Controller) =
if self.flowType == FlowType.AppLogin:
controller.runLoadAccountFlow(true)
method getNextSecondaryState*(self: LoginKeycardEmptyState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardEmptyState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardEmptyState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserNewKeycardKeys, self)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, FlowType.FirstRunNewUserNewKeycardKeys, self)

View File

@ -0,0 +1,40 @@
type
LoginKeycardEnterPinState* = ref object of State
proc newLoginKeycardEnterPinState*(flowType: FlowType, backState: State): LoginKeycardEnterPinState =
result = LoginKeycardEnterPinState()
result.setup(flowType, StateType.LoginKeycardEnterPin, backState)
proc delete*(self: LoginKeycardEnterPinState) =
self.State.delete
method executePrimaryCommand*(self: LoginKeycardEnterPinState, controller: Controller) =
if self.flowType == FlowType.AppLogin:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
method getNextSecondaryState*(self: LoginKeycardEnterPinState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardEnterPinState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardEnterPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard()
return
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return createState(StateType.LoginKeycardWrongPin, self.flowType, nil)
return createState(StateType.LoginKeycardMaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.LoginKeycardMaxPinRetriesReached, self.flowType, nil)

View File

@ -0,0 +1,21 @@
type
LoginKeycardInsertKeycardState* = ref object of State
proc newLoginKeycardInsertKeycardState*(flowType: FlowType, backState: State): LoginKeycardInsertKeycardState =
result = LoginKeycardInsertKeycardState()
result.setup(flowType, StateType.LoginKeycardInsertKeycard, backState)
proc delete*(self: LoginKeycardInsertKeycardState) =
self.State.delete
method getNextSecondaryState*(self: LoginKeycardInsertKeycardState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardInsertKeycardState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardInsertKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if keycardFlowType == ResponseTypeValueCardInserted:
return createState(StateType.LoginKeycardReadingKeycard, self.flowType, nil)
return nil

View File

@ -0,0 +1,22 @@
type
LoginKeycardMaxPinRetriesReachedState* = ref object of State
proc newLoginKeycardMaxPinRetriesReachedState*(flowType: FlowType, backState: State): LoginKeycardMaxPinRetriesReachedState =
result = LoginKeycardMaxPinRetriesReachedState()
result.setup(flowType, StateType.LoginKeycardMaxPinRetriesReached, backState)
proc delete*(self: LoginKeycardMaxPinRetriesReachedState) =
self.State.delete
method executeBackCommand*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
controller.runLoginFlow()
method getNextPrimaryState*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller): State =
return createState(StateType.KeycardRecover, self.flowType, self)
method getNextSecondaryState*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardMaxPinRetriesReachedState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)

View File

@ -0,0 +1,12 @@
type
LoginKeycardMaxPukRetriesReachedState* = ref object of State
proc newLoginKeycardMaxPukRetriesReachedState*(flowType: FlowType, backState: State): LoginKeycardMaxPukRetriesReachedState =
result = LoginKeycardMaxPukRetriesReachedState()
result.setup(flowType, StateType.LoginKeycardMaxPukRetriesReached, backState)
proc delete*(self: LoginKeycardMaxPukRetriesReachedState) =
self.State.delete
method getNextPrimaryState*(self: LoginKeycardMaxPukRetriesReachedState, controller: Controller): State =
return createState(StateType.UserProfileEnterSeedPhrase, self.flowType, nil)

View File

@ -0,0 +1,55 @@
import ../../../global/global_singleton
type
LoginKeycardReadingKeycardState* = ref object of State
proc newLoginKeycardReadingKeycardState*(flowType: FlowType, backState: State): LoginKeycardReadingKeycardState =
result = LoginKeycardReadingKeycardState()
result.setup(flowType, StateType.LoginKeycardReadingKeycard, backState)
proc delete*(self: LoginKeycardReadingKeycardState) =
self.State.delete
method executePrimaryCommand*(self: LoginKeycardReadingKeycardState, controller: Controller) =
if not controller.keychainErrorOccurred():
controller.enterKeycardPin(controller.getPin())
method getNextPrimaryState*(self: LoginKeycardReadingKeycardState, controller: Controller): State =
if controller.keychainErrorOccurred():
return createState(StateType.LoginKeycardEnterPin, self.flowType, nil)
method getNextSecondaryState*(self: LoginKeycardReadingKeycardState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardReadingKeycardState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardReadingKeycardState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard()
return nil
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len == 0:
if not controller.keyUidMatch(keycardEvent.keyUid):
return createState(StateType.LoginKeycardWrongKeycard, self.flowType, nil)
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if value == LS_VALUE_STORE:
controller.tryToObtainDataFromKeychain()
return nil
return createState(StateType.LoginKeycardEnterPin, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.LoginKeycardMaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorNoKeys:
return createState(StateType.LoginKeycardEmpty, self.flowType, nil)
if keycardFlowType == ResponseTypeValueSwapCard and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPUKRetries:
return createState(StateType.LoginKeycardMaxPukRetriesReached, self.flowType, nil)

View File

@ -0,0 +1,15 @@
type
LoginKeycardWrongKeycardState* = ref object of State
proc newLoginKeycardWrongKeycardState*(flowType: FlowType, backState: State): LoginKeycardWrongKeycardState =
result = LoginKeycardWrongKeycardState()
result.setup(flowType, StateType.LoginKeycardWrongKeycard, backState)
proc delete*(self: LoginKeycardWrongKeycardState) =
self.State.delete
method getNextSecondaryState*(self: LoginKeycardWrongKeycardState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardWrongKeycardState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)

View File

@ -0,0 +1,43 @@
type
LoginKeycardWrongPinState* = ref object of State
pinValid: bool
proc newLoginKeycardWrongPinState*(flowType: FlowType, backState: State): LoginKeycardWrongPinState =
result = LoginKeycardWrongPinState()
result.setup(flowType, StateType.LoginKeycardWrongPin, backState)
result.pinValid = false
proc delete*(self: LoginKeycardWrongPinState) =
self.State.delete
method executePrimaryCommand*(self: LoginKeycardWrongPinState, controller: Controller) =
if self.flowType == FlowType.AppLogin:
if controller.getPin().len == PINLengthForStatusApp:
controller.enterKeycardPin(controller.getPin())
method getNextSecondaryState*(self: LoginKeycardWrongPinState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginKeycardWrongPinState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginKeycardWrongPinState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueEnterPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == RequestParamPIN:
controller.setKeycardData($keycardEvent.pinRetries)
if keycardEvent.pinRetries > 0:
return nil
return createState(StateType.LoginKeycardMaxPinRetriesReached, self.flowType, nil)
if keycardFlowType == ResponseTypeValueEnterPUK and
keycardEvent.error.len == 0:
if keycardEvent.pinRetries == 0 and keycardEvent.pukRetries > 0:
return createState(StateType.LoginKeycardMaxPinRetriesReached, self.flowType, nil)
return nil
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len == 0:
controller.setKeycardEvent(keycardEvent)
controller.loginAccountKeycard()
return nil

View File

@ -1,7 +1,3 @@
import state
import ../controller
import welcome_state_new_user, welcome_state_old_user
type
LoginState* = ref object of State
@ -12,14 +8,22 @@ proc newLoginState*(flowType: FlowType, backState: State): LoginState =
proc delete*(self: LoginState) =
self.State.delete
method moveToNextPrimaryState*(self: LoginState): bool =
return false
method executePrimaryCommand*(self: LoginState, controller: Controller) =
controller.login()
method getNextSecondaryState*(self: LoginState): State =
return newWelcomeStateNewUser(FlowType.General, self)
method getNextSecondaryState*(self: LoginState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginState): State =
return newWelcomeStateOldUser(FlowType.General, self)
method getNextTertiaryState*(self: LoginState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, self.flowType, self)
method resolveKeycardNextState*(self: LoginState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return nil
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.LoginKeycardInsertKeycard, self.flowType, nil)

View File

@ -1,5 +1,3 @@
import state, welcome_state
type
NotificationState* = ref object of State
@ -10,5 +8,5 @@ proc newNotificationState*(flowType: FlowType, backState: State): NotificationSt
proc delete*(self: NotificationState) =
self.State.delete
method getNextPrimaryState*(self: NotificationState): State =
return newWelcomeState(FlowType.General, nil)
method getNextPrimaryState*(self: NotificationState, controller: Controller): State =
return createState(StateType.Welcome, FlowType.General, nil)

View File

@ -1,10 +1,14 @@
import ../controller
from ../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
export KeycardEvent, KeyDetails
type FlowType* {.pure.} = enum
General = "General"
FirstRunNewUserNewKeys = "FirstRunNewUserNewKeys"
FirstRunNewUserNewKeycardKeys = "FirstRunNewUserNewKeycardKeys"
FirstRunNewUserImportSeedPhrase = "FirstRunNewUserImportSeedPhrase"
FirstRunNewUserImportSeedPhraseIntoKeycard = "FirstRunNewUserImportSeedPhraseIntoKeycard"
FirstRunOldUserSyncCode = "FirstRunOldUserSyncCode"
FirstRunOldUserKeycardImport = "FirstRunOldUserKeycardImport"
FirstRunOldUserImportSeedPhrase = "FirstRunOldUserImportSeedPhrase"
@ -23,7 +27,35 @@ type StateType* {.pure.} = enum
UserProfileImportSeedPhrase = "UserProfileImportSeedPhrase"
UserProfileEnterSeedPhrase = "UserProfileEnterSeedPhrase"
Biometrics = "Biometrics"
KeycardPluginReader = "KeycardPluginReader"
KeycardInsertKeycard = "KeycardInsertKeycard"
KeycardReadingKeycard = "KeycardReadingKeycard"
KeycardCreatePin = "KeycardCreatePin"
KeycardRepeatPin = "KeycardRepeatPin"
KeycardPinSet = "KeycardPinSet"
KeycardEnterPin = "KeycardEnterPin"
KeycardWrongPin = "KeycardWrongPin"
KeycardEnterPuk = "KeycardEnterPuk"
KeycardWrongPuk = "KeycardWrongPuk"
KeycardDisplaySeedPhrase = "KeycardDisplaySeedPhrase"
KeycardEnterSeedPhraseWords = "KeycardEnterSeedPhraseWords"
KeycardNotEmpty = "KeycardNotEmpty"
KeycardEmpty = "KeycardEmpty"
KeycardLocked = "KeycardLocked"
KeycardRecover = "KeycardRecover"
KeycardMaxPairingSlotsReached = "KeycardMaxPairingSlotsReached"
KeycardMaxPinRetriesReached = "KeycardMaxPinRetriesReached"
KeycardMaxPukRetriesReached = "KeycardMaxPukRetriesReached"
Login = "Login"
LoginKeycardInsertKeycard = "LoginKeycardInsertKeycard"
LoginKeycardReadingKeycard = "LoginKeycardReadingKeycard"
LoginKeycardEnterPin = "LoginKeycardEnterPin"
LoginKeycardWrongKeycard = "LoginKeycardWrongKeycard"
LoginKeycardWrongPin = "LoginKeycardWrongPin"
LoginKeycardMaxPinRetriesReached = "LoginKeycardMaxPinRetriesReached"
LoginKeycardMaxPukRetriesReached = "LoginKeycardMaxPukRetriesReached"
LoginKeycardEmpty = "LoginKeycardEmpty"
## This is the base class for all state we may have in onboarding/login flow.
## We should not instance of this class (in c++ this will be an abstract class).
@ -67,15 +99,15 @@ method displayBackButton*(self: State): bool {.inline base.} =
return not self.backState.isNil
## Returns next state instance in case the "primary" action is triggered
method getNextPrimaryState*(self: State): State {.inline base.} =
method getNextPrimaryState*(self: State, controller: Controller): State {.inline base.} =
return nil
## Returns next state instance in case the "secondary" action is triggered
method getNextSecondaryState*(self: State): State {.inline base.} =
method getNextSecondaryState*(self: State, controller: Controller): State {.inline base.} =
return nil
## Returns next state instance in case the "tertiary" action is triggered
method getNextTertiaryState*(self: State): State {.inline base.} =
method getNextTertiaryState*(self: State, controller: Controller): State {.inline base.} =
return nil
## This method is executed in case "back" button is clicked
@ -94,18 +126,7 @@ method executeSecondaryCommand*(self: State, controller: Controller) {.inline ba
method executeTertiaryCommand*(self: State, controller: Controller) {.inline base.} =
discard
## Returns true if we should move from this state immediatelly when the "primary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextPrimaryState*(self: State): bool {.inline base.} =
return true
## Returns true if we should move from this state immediatelly when the "secondary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextSecondaryState*(self: State): bool {.inline base.} =
return true
## Returns true if we should move from this state immediatelly when the "tertiary" action is triggered,
## in case we need to wait for some other action, or some aync event, this should return false
method moveToNextTertiaryState*(self: State): bool {.inline base.} =
return true
## This method is used for handling aync responses for keycard related states
method resolveKeycardNextState*(self: State, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State {.inline base.} =
return nil

View File

@ -0,0 +1,134 @@
import chronicles
import ../../../../app_service/service/keycard/constants
import ../controller
from ../../../../app_service/service/keycard/service import PINLengthForStatusApp
from ../../../../app_service/service/keycard/service import PUKLengthForStatusApp
import state
logScope:
topics = "startup-module-state-factory"
# Forward declaration
proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State
include biometrics_state
include keycard_create_pin_state
include keycard_display_seed_phrase_state
include keycard_empty_state
include keycard_enter_pin_state
include keycard_enter_puk_state
include keycard_enter_seed_phrase_words_state
include keycard_insert_keycard_state
include keycard_locked_state
include keycard_max_pairing_slots_reached_state
include keycard_max_pin_retries_reached_state
include keycard_max_puk_retries_reached_state
include keycard_not_empty_state
include keycard_pin_set_state
include keycard_plugin_reader_state
include keycard_reading_keycard_state
include keycard_recover_state
include keycard_repeat_pin_state
include keycard_wrong_pin_state
include keycard_wrong_puk_state
include notification_state
include user_profile_chat_key_state
include user_profile_confirm_password_state
include user_profile_create_password_state
include user_profile_create_state
include user_profile_enter_seed_phrase_state
include user_profile_import_seed_phrase_state
include welcome_state_new_user
include welcome_state_old_user
include welcome_state
include login_state
include login_keycard_insert_keycard_state
include login_keycard_reading_keycard_state
include login_keycard_enter_pin_state
include login_keycard_wrong_keycard
include login_keycard_wrong_pin_state
include login_keycard_max_pin_retries_reached_state
include login_keycard_max_puk_retries_reached_state
include login_keycard_empty_state
proc createState*(stateToBeCreated: StateType, flowType: FlowType, backState: State): State =
if stateToBeCreated == StateType.AllowNotifications:
return newNotificationState(flowType, backState)
if stateToBeCreated == StateType.Welcome:
return newWelcomeState(flowType, backState)
if stateToBeCreated == StateType.WelcomeNewStatusUser:
return newWelcomeStateNewUser(flowType, backState)
if stateToBeCreated == StateType.WelcomeOldStatusUser:
return newWelcomeStateOldUser(flowType, backState)
if stateToBeCreated == StateType.UserProfileCreate:
return newUserProfileCreateState(flowType, backState)
if stateToBeCreated == StateType.UserProfileChatKey:
return newUserProfileChatKeyState(flowType, backState)
if stateToBeCreated == StateType.UserProfileCreatePassword:
return newUserProfileCreatePasswordState(flowType, backState)
if stateToBeCreated == StateType.UserProfileConfirmPassword:
return newUserProfileConfirmPasswordState(flowType, backState)
if stateToBeCreated == StateType.UserProfileImportSeedPhrase:
return newUserProfileImportSeedPhraseState(flowType, backState)
if stateToBeCreated == StateType.UserProfileEnterSeedPhrase:
return newUserProfileEnterSeedPhraseState(flowType, backState)
if stateToBeCreated == StateType.Biometrics:
return newBiometricsState(flowType, backState)
if stateToBeCreated == StateType.KeycardPluginReader:
return newKeycardPluginReaderState(flowType, backState)
if stateToBeCreated == StateType.KeycardInsertKeycard:
return newKeycardInsertKeycardState(flowType, backState)
if stateToBeCreated == StateType.KeycardReadingKeycard:
return newKeycardReadingKeycardState(flowType, backState)
if stateToBeCreated == StateType.KeycardCreatePin:
return newKeycardCreatePinState(flowType, backState)
if stateToBeCreated == StateType.KeycardRepeatPin:
return newKeycardRepeatPinState(flowType, backState)
if stateToBeCreated == StateType.KeycardPinSet:
return newKeycardPinSetState(flowType, backState)
if stateToBeCreated == StateType.KeycardEnterPin:
return newKeycardEnterPinState(flowType, backState)
if stateToBeCreated == StateType.KeycardWrongPin:
return newKeycardWrongPinState(flowType, backState)
if stateToBeCreated == StateType.KeycardEnterPuk:
return newKeycardEnterPukState(flowType, backState)
if stateToBeCreated == StateType.KeycardWrongPuk:
return newKeycardWrongPukState(flowType, backState)
if stateToBeCreated == StateType.KeycardDisplaySeedPhrase:
return newKeycardDisplaySeedPhraseState(flowType, backState)
if stateToBeCreated == StateType.KeycardEnterSeedPhraseWords:
return newKeycardEnterSeedPhraseWordsState(flowType, backState)
if stateToBeCreated == StateType.KeycardNotEmpty:
return newKeycardNotEmptyState(flowType, backState)
if stateToBeCreated == StateType.KeycardEmpty:
return newKeycardEmptyState(flowType, backState)
if stateToBeCreated == StateType.KeycardLocked:
return newKeycardLockedState(flowType, backState)
if stateToBeCreated == StateType.KeycardRecover:
return newKeycardRecoverState(flowType, backState)
if stateToBeCreated == StateType.KeycardMaxPairingSlotsReached:
return newKeycardMaxPairingSlotsReachedState(flowType, backState)
if stateToBeCreated == StateType.KeycardMaxPinRetriesReached:
return newKeycardMaxPinRetriesReachedState(flowType, backState)
if stateToBeCreated == StateType.KeycardMaxPukRetriesReached:
return newKeycardMaxPukRetriesReachedState(flowType, backState)
if stateToBeCreated == StateType.Login:
return newLoginState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardInsertKeycard:
return newLoginKeycardInsertKeycardState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardReadingKeycard:
return newLoginKeycardReadingKeycardState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardEnterPin:
return newLoginKeycardEnterPinState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardWrongKeycard:
return newLoginKeycardWrongKeycardState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardWrongPin:
return newLoginKeycardWrongPinState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardMaxPinRetriesReached:
return newLoginKeycardMaxPinRetriesReachedState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardMaxPukRetriesReached:
return newLoginKeycardMaxPukRetriesReachedState(flowType, backState)
if stateToBeCreated == StateType.LoginKeycardEmpty:
return newLoginKeycardEmptyState(flowType, backState)
error "No implementation available for state ", state=stateToBeCreated

View File

@ -1,6 +1,3 @@
import state
import user_profile_create_password_state
type
UserProfileChatKeyState* = ref object of State
@ -11,5 +8,12 @@ proc newUserProfileChatKeyState*(flowType: FlowType, backState: State): UserProf
proc delete*(self: UserProfileChatKeyState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileChatKeyState): State =
return newUserProfileCreatePasswordState(self.State.flowType, self)
method getNextPrimaryState*(self: UserProfileChatKeyState, controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserNewKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhrase or
self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
return createState(StateType.UserProfileCreatePassword, self.flowType, self)
if self.flowType == FlowType.FirstRunNewUserNewKeycardKeys or
self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard or
self.flowType == FlowType.FirstRunOldUserKeycardImport:
return createState(StateType.Biometrics, self.flowType, self)

View File

@ -1,7 +1,3 @@
import state
import biometrics_state
import ../controller
type
UserProfileConfirmPasswordState* = ref object of State
@ -12,23 +8,23 @@ proc newUserProfileConfirmPasswordState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileConfirmPasswordState) =
self.State.delete
method moveToNextPrimaryState*(self: UserProfileConfirmPasswordState): bool =
return defined(macosx)
method getNextPrimaryState*(self: UserProfileConfirmPasswordState): State =
if not self.moveToNextPrimaryState():
method getNextPrimaryState*(self: UserProfileConfirmPasswordState, controller: Controller): State =
if not defined(macosx):
return nil
return newBiometricsState(self.State.flowType, nil)
return createState(StateType.Biometrics, self.flowType, self)
method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) =
if self.moveToNextPrimaryState():
if defined(macosx):
return
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys:
controller.storeGeneratedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
controller.storeGeneratedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunOldUserImportSeedPhrase:
## This should not be the correct call for this flow, this is an issue, but since current implementation is like that
## and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain = false) # false, cause we don't have keychain support for other than mac os
## This should not be the correct call for this flow, this is an issue,
## but since current implementation is like that and this is not a bug fixing issue, left as it is.
controller.storeImportedAccountAndLogin(storeToKeychain)
elif self.flowType == FlowType.FirstRunNewUserNewKeycardKeys:
controller.storeKeycardAccountAndLogin(storeToKeychain)

View File

@ -1,7 +1,3 @@
import state
import ../controller
import user_profile_confirm_password_state
type
UserProfileCreatePasswordState* = ref object of State
@ -12,8 +8,8 @@ proc newUserProfileCreatePasswordState*(flowType: FlowType, backState: State): U
proc delete*(self: UserProfileCreatePasswordState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileCreatePasswordState): State =
return newUserProfileConfirmPasswordState(self.State.flowType, self)
method getNextPrimaryState*(self: UserProfileCreatePasswordState, controller: Controller): State =
return createState(StateType.UserProfileConfirmPassword, self.flowType, self)
method executeBackCommand*(self: UserProfileCreatePasswordState, controller: Controller) =
controller.setPassword("")

View File

@ -1,7 +1,3 @@
import state
import ../controller
import user_profile_chat_key_state
type
UserProfileCreateState* = ref object of State
@ -12,8 +8,8 @@ proc newUserProfileCreateState*(flowType: FlowType, backState: State): UserProfi
proc delete*(self: UserProfileCreateState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileCreateState): State =
return newUserProfileChatKeyState(self.State.flowType, self)
method getNextPrimaryState*(self: UserProfileCreateState, controller: Controller): State =
return createState(StateType.UserProfileChatKey, self.flowType, self)
method executeBackCommand*(self: UserProfileCreateState, controller: Controller) =
controller.setDisplayName("")

View File

@ -1,7 +1,3 @@
import state
import ../controller
import user_profile_create_state
type
UserProfileEnterSeedPhraseState* = ref object of State
successfulImport: bool
@ -14,13 +10,44 @@ proc newUserProfileEnterSeedPhraseState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileEnterSeedPhraseState) =
self.State.delete
method moveToNextPrimaryState*(self: UserProfileEnterSeedPhraseState): bool =
return self.successfulImport
method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState): State =
if not self.moveToNextPrimaryState():
method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState, controller: Controller): State =
if not self.successfulImport:
return nil
return newUserProfileCreateState(self.State.flowType, self)
if self.flowType == FlowType.FirstRunOldUserImportSeedPhrase or
self.flowType == FlowType.FirstRunNewUserImportSeedPhrase:
return createState(StateType.UserProfileCreate, self.flowType, self)
method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller: Controller) =
if self.flowType == FlowType.AppLogin:
controller.runLoadAccountFlowWithSeedPhrase(controller.getSeedPhraseLength(), controller.getSeedPhrase(), true)
else:
self.successfulImport = controller.importMnemonic()
if self.successfulImport:
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
controller.storeSeedPhraseToKeycard(controller.getSeedPhraseLength(), controller.getSeedPhrase())
elif self.flowType == FlowType.FirstRunOldUserKeycardImport:
controller.runLoadAccountFlowWithSeedPhrase(controller.getSeedPhraseLength(), controller.getSeedPhrase(), true)
method resolveKeycardNextState*(self: UserProfileEnterSeedPhraseState, keycardFlowType: string, keycardEvent: KeycardEvent,
controller: Controller): State =
if self.flowType == FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard:
if keycardFlowType == ResponseTypeValueEnterNewPIN and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorRequireInit:
return createState(StateType.KeycardCreatePin, self.flowType, self.getBackState)
if self.flowType == FlowType.FirstRunOldUserKeycardImport:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, self.flowType, self)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, self.flowType, self.getBackState)
if self.flowType == FlowType.AppLogin:
if keycardFlowType == ResponseTypeValueKeycardFlowResult and
keycardEvent.error.len > 0 and
keycardEvent.error == ErrorConnection:
controller.resumeCurrentFlowLater()
return createState(StateType.KeycardPluginReader, self.flowType, self)
if keycardFlowType == ResponseTypeValueInsertCard:
return createState(StateType.KeycardInsertKeycard, self.flowType, self.getBackState)

View File

@ -1,6 +1,3 @@
import state
import user_profile_enter_seed_phrase_state
type
UserProfileImportSeedPhraseState* = ref object of State
@ -11,5 +8,8 @@ proc newUserProfileImportSeedPhraseState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileImportSeedPhraseState) =
self.State.delete
method getNextPrimaryState*(self: UserProfileImportSeedPhraseState): State =
return newUserProfileEnterSeedPhraseState(self.State.flowType, self)
method getNextPrimaryState*(self: UserProfileImportSeedPhraseState, controller: Controller): State =
return createState(StateType.UserProfileEnterSeedPhrase, FlowType.FirstRunNewUserImportSeedPhrase, self)
method getNextSecondaryState*(self: UserProfileImportSeedPhraseState, controller: Controller): State =
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserImportSeedPhraseIntoKeycard, self)

View File

@ -1,6 +1,3 @@
import state
import welcome_state_new_user, welcome_state_old_user
type
WelcomeState* = ref object of State
@ -11,8 +8,8 @@ proc newWelcomeState*(flowType: FlowType, backState: State): WelcomeState =
proc delete*(self: WelcomeState) =
self.State.delete
method getNextPrimaryState*(self: WelcomeState): State =
return newWelcomeStateNewUser(FlowType.General, self)
method getNextPrimaryState*(self: WelcomeState, controller: Controller): State =
return createState(StateType.WelcomeNewStatusUser, FlowType.General, self)
method getNextSecondaryState*(self: WelcomeState): State =
return newWelcomeStateOldUser(FlowType.General, self)
method getNextSecondaryState*(self: WelcomeState, controller: Controller): State =
return createState(StateType.WelcomeOldStatusUser, FlowType.General, self)

View File

@ -1,6 +1,3 @@
import state
import user_profile_create_state, user_profile_import_seed_phrase_state
type
WelcomeStateNewUser* = ref object of State
@ -11,14 +8,15 @@ proc newWelcomeStateNewUser*(flowType: FlowType, backState: State): WelcomeState
proc delete*(self: WelcomeStateNewUser) =
self.State.delete
method getNextPrimaryState*(self: WelcomeStateNewUser): State =
return newUserProfileCreateState(FlowType.FirstRunNewUserNewKeys, self)
method executeBackCommand*(self: WelcomeStateNewUser, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
controller.runLoginFlow()
method getNextSecondaryState*(self: WelcomeStateNewUser): State =
# We will handle here a click on `Generate keys for a new Keycard`
discard
method getNextTertiaryState*(self: WelcomeStateNewUser): State =
return newUserProfileImportSeedPhraseState(FlowType.FirstRunNewUserImportSeedPhrase, self)
method getNextPrimaryState*(self: WelcomeStateNewUser, controller: Controller): State =
return createState(StateType.UserProfileCreate, FlowType.FirstRunNewUserNewKeys, self)
method getNextSecondaryState*(self: WelcomeStateNewUser, controller: Controller): State =
return createState(StateType.KeycardPluginReader, FlowType.FirstRunNewUserNewKeycardKeys, self)
method getNextTertiaryState*(self: WelcomeStateNewUser, controller: Controller): State =
return createState(StateType.UserProfileImportSeedPhrase, FlowType.General, self)

View File

@ -1,6 +1,3 @@
import state
import user_profile_enter_seed_phrase_state
type
WelcomeStateOldUser* = ref object of State
@ -11,19 +8,17 @@ proc newWelcomeStateOldUser*(flowType: FlowType, backState: State): WelcomeState
proc delete*(self: WelcomeStateOldUser) =
self.State.delete
method getNextPrimaryState*(self: WelcomeStateOldUser): State =
method executeBackCommand*(self: WelcomeStateOldUser, controller: Controller) =
if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
controller.runLoginFlow()
method getNextPrimaryState*(self: WelcomeStateOldUser, controller: Controller): State =
# We will handle here a click on `Scan sync code`
discard
method getNextSecondaryState*(self: WelcomeStateOldUser): State =
# We will handle here a click on `Login with Keycard`
discard
method getNextSecondaryState*(self: WelcomeStateOldUser, controller: Controller): State =
return createState(StateType.KeycardPluginReader, FlowType.FirstRunOldUserKeycardImport, self)
method getNextTertiaryState*(self: WelcomeStateOldUser): State =
## This is added as next state in case of import seed for an old user, but this doesn't match the flow
## in the design. Need to be fixed correctly.
## Why it's not fixed now???
## -> Cause this is just a improving and moving to a better form what we currently have, fixing will be done in another issue
## and need to be discussed as we haven't had that flow implemented ever before
return newUserProfileEnterSeedPhraseState(FlowType.FirstRunOldUserImportSeedPhrase, self)
method getNextTertiaryState*(self: WelcomeStateOldUser, controller: Controller): State =
return createState(StateType.UserProfileEnterSeedPhrase, FlowType.FirstRunOldUserImportSeedPhrase, self)

View File

@ -1,5 +1,7 @@
import ../../../app_service/service/accounts/service
import ../../../app_service/service/accounts/service as accounts_service
import models/login_account_item as login_acc_item
from ../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -37,6 +39,9 @@ method getImportedAccount*(self: AccessInterface): GeneratedAccountDto {.base.}
method generateImage*(self: AccessInterface, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.base.} =
raise newException(ValueError, "No implementation available")
method getCroppedProfileImage*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setDisplayName*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
@ -49,6 +54,15 @@ method setPassword*(self: AccessInterface, value: string) {.base.} =
method getPassword*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setPin*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method setPuk*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getPin*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method getPasswordStrengthScore*(self: AccessInterface, password: string, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available")
@ -73,12 +87,24 @@ method onNodeLogin*(self: AccessInterface, error: string) {.base.} =
method emitAccountLoginError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordError*(self: AccessInterface, errorDescription: string) {.base.} =
method emitObtainingPasswordError*(self: AccessInterface, errorDescription: string, errorType: string) {.base.} =
raise newException(ValueError, "No implementation available")
method emitObtainingPasswordSuccess*(self: AccessInterface, password: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onKeycardResponse*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} =
raise newException(ValueError, "No implementation available")
method checkRepeatedKeycardPinWhileTyping*(self: AccessInterface, pin: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getSeedPhrase*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method setKeycardData*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c

View File

@ -12,8 +12,10 @@ type
colorHash: color_hash_model.Model
colorHashVariant: QVariant
colorId: int
keycardPairing: string
proc initItem*(name, thumbnailImage, largeImage, keyUid: string, colorHash: seq[ColorHashSegment], colorId: int):
proc initItem*(name, thumbnailImage, largeImage, keyUid: string, colorHash: seq[ColorHashSegment], colorId: int,
keycardPairing: string):
Item =
result.name = name
result.thumbnailImage = thumbnailImage
@ -23,6 +25,7 @@ proc initItem*(name, thumbnailImage, largeImage, keyUid: string, colorHash: seq[
result.colorHash.setItems(map(colorHash, x => color_hash_item.initItem(x.len, x.colorIdx)))
result.colorHashVariant = newQVariant(result.colorHash)
result.colorId = colorId
result.keycardPairing = keycardPairing
proc getName*(self: Item): string =
return self.name
@ -44,3 +47,9 @@ proc getColorHashVariant*(self: Item): QVariant =
proc getColorId*(self: Item): int =
return self.colorId
proc getKeycardPairing*(self: Item): string =
return self.keycardPairing
proc getKeycardCreatedAccount*(self: Item): bool =
return self.keycardPairing.len > 0

View File

@ -10,6 +10,8 @@ type
KeyUid
ColorHash
ColorId
KeycardPairing
KeycardCreatedAccount
QtObject:
type
@ -37,7 +39,9 @@ QtObject:
ModelRole.LargeImage.int:"largeImage",
ModelRole.KeyUid.int:"keyUid",
ModelRole.ColorHash.int:"colorHash",
ModelRole.ColorId.int:"colorId"
ModelRole.ColorId.int:"colorId",
ModelRole.KeycardPairing.int:"keycardPairing",
ModelRole.KeycardCreatedAccount.int:"keycardCreatedAccount"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -63,6 +67,10 @@ QtObject:
result = newQVariant(item.getColorHash())
of ModelRole.ColorId:
result = newQVariant(item.getColorId())
of ModelRole.KeycardPairing:
result = newQVariant(item.getKeycardPairing())
of ModelRole.KeycardCreatedAccount:
result = newQVariant(item.getKeycardCreatedAccount())
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()

View File

@ -2,7 +2,7 @@ import NimQml, chronicles
import io_interface
import view, controller
import internal/[state, notification_state, welcome_state, login_state]
import internal/[state, state_factory]
import models/generated_account_item as gen_acc_item
import models/login_account_item as login_acc_item
import ../../global/global_singleton
@ -12,6 +12,7 @@ import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/accounts/service as accounts_service
import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/profile/service as profile_service
import ../../../app_service/service/keycard/service as keycard_service
export io_interface
@ -30,14 +31,15 @@ proc newModule*[T](delegate: T,
keychainService: keychain_service.Service,
accountsService: accounts_service.Service,
generalService: general_service.Service,
profileService: profile_service.Service):
profileService: profile_service.Service,
keycardService: keycard_service.Service):
Module[T] =
result = Module[T]()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, generalService, accountsService, keychainService,
profileService)
profileService, keycardService)
method delete*[T](self: Module[T]) =
self.view.delete
@ -69,16 +71,18 @@ method load*[T](self: Module[T]) =
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
else:
let openedAccounts = self.controller.getOpenedAccounts()
if(openedAccounts.len > 0):
var items: seq[login_acc_item.Item]
for acc in openedAccounts:
var thumbnailImage: string
var largeImage: string
self.extractImages(acc, thumbnailImage, largeImage)
items.add(login_acc_item.initItem(acc.name, thumbnailImage, largeImage, acc.keyUid, acc.colorHash, acc.colorId))
items.add(login_acc_item.initItem(acc.name, thumbnailImage, largeImage, acc.keyUid, acc.colorHash, acc.colorId,
acc.keycardPairing))
self.view.setLoginAccountsModelItems(items)
# set the first account as slected one
if items.len == 0:
# we should never be here, since else block of `if (shouldStartWithOnboardingScreen)`
# ensures that `openedAccounts` is not empty array
error "cannot run the app in login flow cause list of login accounts is empty"
quit() # quit the app
self.setSelectedLoginAccount(items[0])
@ -96,35 +100,54 @@ method emitLogOut*[T](self: Module[T]) =
method onBackActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
if currStateObj.isNil:
error "cannot resolve current state"
return
debug "back_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.executeBackCommand(self.controller)
let backState = currStateObj.getBackState()
self.view.setCurrentStartupState(backState)
debug "back_action - set state", setCurrFlow=backState.flowType(), newCurrState=backState.stateType()
currStateObj.delete()
method onPrimaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
if currStateObj.isNil:
error "cannot resolve current state"
return
debug "primary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.executePrimaryCommand(self.controller)
if currStateObj.moveToNextPrimaryState():
let nextState = currStateObj.getNextPrimaryState()
let nextState = currStateObj.getNextPrimaryState(self.controller)
if nextState.isNil:
return
self.view.setCurrentStartupState(nextState)
debug "primary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method onSecondaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
if currStateObj.isNil:
error "cannot resolve current state"
return
debug "secondary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.executeSecondaryCommand(self.controller)
if currStateObj.moveToNextSecondaryState():
let nextState = currStateObj.getNextSecondaryState()
let nextState = currStateObj.getNextSecondaryState(self.controller)
if nextState.isNil:
return
self.view.setCurrentStartupState(nextState)
debug "secondary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method onTertiaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil:
if currStateObj.isNil:
error "cannot resolve current state"
return
debug "tertiary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.executeTertiaryCommand(self.controller)
if currStateObj.moveToNextTertiaryState():
let nextState = currStateObj.getNextTertiaryState()
let nextState = currStateObj.getNextTertiaryState(self.controller)
if nextState.isNil:
return
self.view.setCurrentStartupState(nextState)
debug "tertiary_action - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method getImportedAccount*[T](self: Module[T]): GeneratedAccountDto =
return self.controller.getImportedAccount()
@ -132,6 +155,9 @@ method getImportedAccount*[T](self: Module[T]): GeneratedAccountDto =
method generateImage*[T](self: Module[T], imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
return self.controller.generateImage(imageUrl, aX, aY, bX, bY)
method getCroppedProfileImage*[T](self: Module[T]): string =
return self.controller.getCroppedProfileImage()
method setDisplayName*[T](self: Module[T], value: string) =
self.controller.setDisplayName(value)
@ -144,6 +170,15 @@ method setPassword*[T](self: Module[T], value: string) =
method getPassword*[T](self: Module[T]): string =
return self.controller.getPassword()
method setPin*[T](self: Module[T], value: string) =
self.controller.setPin(value)
method setPuk*[T](self: Module[T], value: string) =
self.controller.setPuk(value)
method getPin*[T](self: Module[T]): string =
return self.controller.getPin()
method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int =
return self.controller.getPasswordStrengthScore(password, userName)
@ -161,13 +196,18 @@ method importAccountSuccess*[T](self: Module[T]) =
method setSelectedLoginAccount*[T](self: Module[T], item: login_acc_item.Item) =
self.controller.setSelectedLoginAccountKeyUid(item.getKeyUid())
if item.getKeycardCreatedAccount():
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil)) # nim garbage collector will handle all abandoned state objects
self.controller.runLoginFlow()
else:
self.controller.tryToObtainDataFromKeychain()
self.view.setSelectedLoginAccount(item)
method emitAccountLoginError*[T](self: Module[T], error: string) =
self.view.emitAccountLoginError(error)
method emitObtainingPasswordError*[T](self: Module[T], errorDescription: string) =
self.view.emitObtainingPasswordError(errorDescription)
method emitObtainingPasswordError*[T](self: Module[T], errorDescription: string, errorType: string) =
self.view.emitObtainingPasswordError(errorDescription, errorType)
method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) =
self.view.emitObtainingPasswordSuccess(password)
@ -175,7 +215,7 @@ method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) =
method onNodeLogin*[T](self: Module[T], error: string) =
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "error: cannot determine current startup state"
error "cannot determine current startup state"
quit() # quit the app
if error.len == 0:
@ -187,4 +227,37 @@ method onNodeLogin*[T](self: Module[T], error: string) =
self.emitAccountLoginError(error)
else:
self.setupAccountError(error)
error "error: ", methodName="onNodeLogin", errDesription =error
error "login error", methodName="onNodeLogin", errDesription =error
method onKeycardResponse*[T](self: Module[T], keycardFlowType: string, keycardEvent: KeycardEvent) =
let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil:
error "cannot resolve current state"
return
debug "on_keycard_response", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
let nextState = currStateObj.resolveKeycardNextState(keycardFlowType, keycardEvent, self.controller)
if nextState.isNil:
return
self.view.setCurrentStartupState(nextState)
debug "on_keycard_response - set state", setCurrFlow=nextState.flowType(), setCurrState=nextState.stateType()
method checkRepeatedKeycardPinWhileTyping*[T](self: Module[T], pin: string): bool =
self.controller.setPinMatch(false)
let storedPin = self.controller.getPin()
if pin.len > storedPin.len:
return false
elif pin.len < storedPin.len:
for i in 0 ..< pin.len:
if pin[i] != storedPin[i]:
return false
return true
else:
let match = pin == storedPin
self.controller.setPinMatch(match)
return match
method getSeedPhrase*[T](self: Module[T]): string =
return self.controller.getSeedPhrase()
method setKeycardData*[T](self: Module[T], value: string) =
self.view.setKeycardData(value)

View File

@ -26,30 +26,35 @@ QtObject:
proc getKeyUid(self: SelectedLoginAccount): string {.slot.} =
return self.item.getKeyUid()
QtProperty[string] keyUid:
read = getKeyUid
proc getKeycardPairing(self: SelectedLoginAccount): string {.slot.} =
return self.item.getKeycardPairing()
QtProperty[string] keycardPairing:
read = getKeycardPairing
proc getKeycardCreatedAccount(self: SelectedLoginAccount): bool {.slot.} =
return self.item.getKeycardPairing().len > 0
QtProperty[bool] keycardCreatedAccount:
read = getKeycardCreatedAccount
proc getColorHash(self: SelectedLoginAccount): QVariant {.slot.} =
return self.item.getColorHashVariant()
QtProperty[QVariant] colorHash:
read = getColorHash
proc getColorId(self: SelectedLoginAccount): int {.slot.} =
return self.item.getColorId()
QtProperty[int] colorId:
read = getColorId
proc getThumbnailImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getThumbnailImage()
QtProperty[string] thumbnailImage:
read = getThumbnailImage
proc getLargeImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getLargeImage()
QtProperty[string] largeImage:
read = getLargeImage

View File

@ -26,6 +26,7 @@ QtObject:
loginAccountsModel: login_acc_model.Model
loginAccountsModelVariant: QVariant
appState: AppState
keycardData: string # used to temporary store the data coming from keycard, depends on current state different data may be stored
proc delete*(self: View) =
self.currentStartupStateVariant.delete
@ -133,7 +134,10 @@ QtObject:
notify = importedAccountChanged
proc generateImage*(self: View, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
self.delegate.generateImage(imageUrl, aX, aY, bX, bY)
return self.delegate.generateImage(imageUrl, aX, aY, bX, bY)
proc getCroppedProfileImage*(self: View): string {.slot.} =
return self.delegate.getCroppedProfileImage()
proc setDisplayName*(self: View, value: string) {.slot.} =
self.delegate.setDisplayName(value)
@ -147,6 +151,15 @@ QtObject:
proc getPassword*(self: View): string {.slot.} =
return self.delegate.getPassword()
proc setPin*(self: View, value: string) {.slot.} =
self.delegate.setPin(value)
proc getPin*(self: View): string {.slot.} =
return self.delegate.getPin()
proc setPuk*(self: View, value: string) {.slot.} =
self.delegate.setPuk(value)
proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName)
@ -194,10 +207,28 @@ QtObject:
proc emitAccountLoginError*(self: View, error: string) =
self.accountLoginError(error)
proc obtainingPasswordError*(self:View, errorDescription: string) {.signal.}
proc emitObtainingPasswordError*(self: View, errorDescription: string) =
self.obtainingPasswordError(errorDescription)
proc obtainingPasswordError*(self:View, errorDescription: string, errorType: string) {.signal.}
proc emitObtainingPasswordError*(self: View, errorDescription: string, errorType: string) =
self.obtainingPasswordError(errorDescription, errorType)
proc obtainingPasswordSuccess*(self:View, password: string) {.signal.}
proc emitObtainingPasswordSuccess*(self: View, password: string) =
self.obtainingPasswordSuccess(password)
proc checkRepeatedKeycardPinWhileTyping*(self: View, pin: string): bool {.slot.} =
return self.delegate.checkRepeatedKeycardPinWhileTyping(pin)
proc getSeedPhrase*(self: View): string {.slot.} =
return self.delegate.getSeedPhrase()
proc keycardDataChanged*(self: View) {.signal.}
proc setKeycardData*(self: View, value: string) =
if self.keycardData == value:
return
self.keycardData = value
self.keycardDataChanged()
proc getKeycardData*(self: View): string {.slot.} =
return self.keycardData
QtProperty[string] keycardData:
read = getKeycardData
notify = keycardDataChanged

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,10 @@
import os, json, sequtils, strutils, uuids
import os, json, sequtils, strutils, uuids, times
import json_serialization, chronicles
import ../../../app/global/global_singleton
import ./dto/accounts as dto_accounts
import ./dto/generated_accounts as dto_generated_accounts
from ../keycard/service import KeycardEvent, KeyDetails
import ../../../backend/general as status_general
import ../../../backend/core as status_core
@ -143,6 +144,25 @@ proc saveAccountAndLogin(self: Service, hashedPassword: string, account,
except Exception as e:
error "error: ", procName="saveAccountAndLogin", errName = e.name, errDesription = e.msg
proc saveKeycardAccountAndLogin(self: Service, chatKey, password: string, account, subaccounts, settings,
config: JsonNode): AccountDto =
try:
let response = status_account.saveAccountAndLoginWithKeycard(chatKey, password, account, subaccounts, settings, config)
var error = "response doesn't contain \"error\""
if(response.result.contains("error")):
error = response.result["error"].getStr
if error == "":
debug "Account saved succesfully"
result = toAccountDto(account)
return
let err = "Error saving account and logging in via keycard : " & error
error "error: ", procName="saveKeycardAccountAndLogin", errDesription = err
except Exception as e:
error "error: ", procName="saveKeycardAccountAndLogin", errName = e.name, errDesription = e.msg
proc prepareAccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string): JsonNode =
result = %* {
"name": if displayName == "": account.alias else: displayName,
@ -259,15 +279,32 @@ proc setLocalAccountSettingsFile(self: Service) =
if(defined(macosx) and self.getLoggedInAccount.isValid()):
singletonInstance.localAccountSettings.setFileName(self.getLoggedInAccount.name)
proc setupAccount*(self: Service, accountId, password, displayName: string): string =
proc addKeycardDetails(self: Service, settingsJson: var JsonNode, accountData: var JsonNode) =
let keycardPairingJsonString = readFile(main_constants.KEYCARDPAIRINGDATAFILE)
let keycardPairingJsonObj = keycardPairingJsonString.parseJSON
let now = now().toTime().toUnix()
for instanceUid, kcDataObj in keycardPairingJsonObj:
if not settingsJson.isNil:
settingsJson["keycard-instance-uid"] = %* instanceUid
settingsJson["keycard-paired-on"] = %* now
settingsJson["keycard-pairing"] = kcDataObj{"key"}
if not accountData.isNil:
accountData["keycard-pairing"] = kcDataObj{"key"}
proc setupAccount*(self: Service, accountId, password, displayName: string, keycardUsage: bool): string =
try:
let installationId = $genUUID()
let accountDataJson = self.getAccountDataForAccountId(accountId, displayName)
var accountDataJson = self.getAccountDataForAccountId(accountId, displayName)
var usedPassword = password
if password.len == 0:
# this means we're setting up an account using keycard
usedPassword = accountDataJson{"key-uid"}.getStr
self.setKeyStoreDir(accountDataJson{"key-uid"}.getStr)
let subaccountDataJson = self.getSubaccountDataForAccountId(accountId, displayName)
let settingsJson = self.getAccountSettings(accountId, installationId, displayName)
var settingsJson = self.getAccountSettings(accountId, installationId, displayName)
let nodeConfigJson = self.getDefaultNodeConfig(installationId)
if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or
@ -276,10 +313,13 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): str
error "error: ", procName="setupAccount", errDesription = description
return description
let hashedPassword = hashString(password)
let hashedPassword = hashString(usedPassword)
discard self.storeAccount(accountId, hashedPassword)
discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS)
if keycardUsage:
self.addKeycardDetails(settingsJson, accountDataJson)
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson,
subaccountDataJson, settingsJson, nodeConfigJson)
self.setLocalAccountSettingsFile()
@ -292,7 +332,83 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): str
error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg
return e.msg
proc setupAccountKeycard*(self: Service, keycardData: KeycardEvent) =
try:
let installationId = $genUUID()
let alias = generateAliasFromPk(keycardData.whisperKey.publicKey)
var accountDataJson = %* {
"name": alias,
"address": keycardData.masterKey.address,
"key-uid": keycardData.keyUid
}
self.setKeyStoreDir(keycardData.keyUid)
let nodeConfigJson = self.getDefaultNodeConfig(installationId)
let subaccountDataJson = %* [
{
"public-key": keycardData.walletKey.publicKey,
"address": keycardData.walletKey.address,
"color": "#4360df",
"wallet": true,
"path": PATH_DEFAULT_WALLET,
"name": "Status account",
"derived-from": keycardData.masterKey.address,
},
{
"public-key": keycardData.whisperKey.publicKey,
"address": keycardData.whisperKey.address,
"name": alias,
"path": PATH_WHISPER,
"chat": true,
"derived-from": ""
}
]
var settingsJson = %* {
"key-uid": keycardData.keyUid,
"public-key": keycardData.whisperKey.publicKey,
"name": alias,
"display-name": "",
"address": keycardData.whisperKey.address,
"eip1581-address": keycardData.eip1581Key.address,
"dapps-address": keycardData.walletKey.address,
"wallet-root-address": keycardData.walletRootKey.address,
"preview-privacy?": true,
"signing-phrase": generateSigningPhrase(3),
"log-level": $LogLevel.INFO,
"latest-derived-path": 0,
"currency": "usd",
"networks/networks": @[],
"networks/current-network": "",
"wallet/visible-tokens": {},
"waku-enabled": true,
"appearance": 0,
"installation-id": installationId
}
self.addKeycardDetails(settingsJson, accountDataJson)
if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or
nodeConfigJson.isNil):
let description = "at least one json object is not prepared well"
error "error: ", procName="setupAccountKeycard", errDesription = description
return
let hashedPassword = hashString(keycardData.keyUid) # using hashed keyUid as password
self.loggedInAccount = self.saveKeycardAccountAndLogin(keycardData.whisperKey.privateKey,
hashedPassword,
accountDataJson,
subaccountDataJson,
settingsJson,
nodeConfigJson)
except Exception as e:
error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg
proc importMnemonic*(self: Service, mnemonic: string): string =
if mnemonic.len == 0:
return "empty mnemonic"
try:
let response = status_account.multiAccountImportMnemonic(mnemonic)
self.importedAccount = toGeneratedAccountDto(response.result)
@ -376,6 +492,35 @@ proc login*(self: Service, account: AccountDto, password: string): string =
error "error: ", procName="login", errName = e.name, errDesription = e.msg
return e.msg
proc loginAccountKeycard*(self: Service, keycardData: KeycardEvent): string =
try:
self.setKeyStoreDir(keycardData.keyUid)
let alias = generateAliasFromPk(keycardData.whisperKey.publicKey)
var accountDataJson = %* {
"name": alias,
"address": keycardData.masterKey.address,
"key-uid": keycardData.keyUid
}
var settingsJson: JsonNode
self.addKeycardDetails(settingsJson, accountDataJson)
let hashedPassword = hashString(keycardData.keyUid) # using hashed keyUid as password
let response = status_account.loginWithKeycard(keycardData.whisperKey.privateKey,
hashedPassword,
accountDataJson)
var error = "response doesn't contain \"error\""
if(response.result.contains("error")):
error = response.result["error"].getStr
if error == "":
debug "Account logged in succesfully"
return
except Exception as e:
error "error: ", procName="loginAccountKeycard", errName = e.name, errDesription = e.msg
return e.msg
proc verifyAccountPassword*(self: Service, account: string, password: string): bool =
try:
let response = status_account.verifyAccountPassword(account, password, self.keyStoreDir)

View File

@ -81,16 +81,6 @@ proc keys*(obj: JsonNode): seq[string] =
for k, _ in obj:
result.add k
proc generateSigningPhrase*(count: int): string =
let now = getTime()
var rng = initRand(now.toUnix * 1000000000 + now.nanosecond)
var phrases: seq[string] = @[]
for i in 1..count:
phrases.add(rng.sample(signing_phrases.phrases))
result = phrases.join(" ")
proc handleRPCErrors*(response: string) =
let parsedReponse = parseJson(response)
if (parsedReponse.hasKey("error")):

View File

@ -0,0 +1,12 @@
#################################################
# Async timer
#################################################
type
TimerTaskArg = ref object of QObjectTaskArg
timeoutInMilliseconds: int
const timerTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[TimerTaskArg](argEncoded)
sleep(arg.timeoutInMilliseconds)
arg.finish("")

View File

@ -0,0 +1,66 @@
const ResponseKeyType* = "type"
const ResponseKeyEvent* = "event"
const ResponseTypeValueKeycardFlowResult* = "keycard.flow-result"
const ResponseTypeValueInsertCard* = "keycard.action.insert-card"
const ResponseTypeValueCardInserted* = "keycard.action.card-inserted"
const ResponseTypeValueSwapCard* = "keycard.action.swap-card"
const ResponseTypeValueEnterPairing* = "keycard.action.enter-pairing"
const ResponseTypeValueEnterPIN* = "keycard.action.enter-pin"
const ResponseTypeValueEnterPUK* = "keycard.action.enter-puk"
const ResponseTypeValueEnterNewPair* = "keycard.action.enter-new-pairing"
const ResponseTypeValueEnterNewPIN* = "keycard.action.enter-new-pin"
const ResponseTypeValueEnterNewPUK* = "keycard.action.enter-new-puk"
const ResponseTypeValueEnterTXHash* = "keycard.action.enter-tx-hash"
const ResponseTypeValueEnterPath* = "keycard.action.enter-bip44-path"
const ResponseTypeValueEnterMnemonic* = "keycard.action.enter-mnemonic"
const ErrorKey* = "error"
const ErrorOK* = "ok"
const ErrorCancel* = "cancel"
const ErrorConnection* = "connection-error"
const ErrorUnknownFlow* = "unknown-flow"
const ErrorNotAKeycard* = "not-a-keycard"
const ErrorNoKeys* = "no-keys"
const ErrorHasKeys* = "has-keys"
const ErrorRequireInit* = "require-init"
const ErrorPairing* = "pairing"
const ErrorUnblocking* = "unblocking"
const ErrorSigning* = "signing"
const ErrorExporting* = "exporting"
const ErrorChanging* = "changing-credentials"
const ErrorLoadingKeys* = "loading-keys"
const RequestParamAppInfo* = "application-info"
const RequestParamInstanceUID* = "instance-uid"
const RequestParamFactoryReset* = "factory reset"
const RequestParamKeyUID* = "key-uid"
const RequestParamFreeSlots* = "free-pairing-slots"
const RequestParamPINRetries* = "pin-retries"
const RequestParamPUKRetries* = "puk-retries"
const RequestParamPairingPass* = "pairing-pass"
const RequestParamPaired* = "paired"
const RequestParamNewPairing* = "new-pairing-pass"
const RequestParamDefPairing* = "KeycardDefaultPairing"
const RequestParamPIN* = "pin"
const RequestParamNewPIN* = "new-pin"
const RequestParamPUK* = "puk"
const RequestParamNewPUK* = "new-puk"
const RequestParamMasterKey* = "master-key"
const RequestParamWalleRootKey* = "wallet-root-key"
const RequestParamWalletKey* = "wallet-key"
const RequestParamEIP1581Key* = "eip1581-key"
const RequestParamWhisperKey* = "whisper-key"
const RequestParamEncKey* = "encryption-key"
const RequestParamExportedKey* = "exported-key"
const RequestParamMnemonic* = "mnemonic"
const RequestParamMnemonicLen* = "mnemonic-length"
const RequestParamMnemonicIdxs* = "mnemonic-indexes"
const RequestParamTXHash* = "tx-hash"
const RequestParamBIP44Path* = "bip44-path"
const RequestParamTXSignature* = "tx-signature"
const RequestParamOverwrite* = "overwrite"
const RequestParamAddress* = "address"
const RequestParamPublicKey* = "publicKey"
const RequestParamPrivateKey* = "privateKey"

View File

@ -0,0 +1,57 @@
type
KeyDetails* = object
address*: string
publicKey*: string
privateKey*: string
KeycardEvent* = object
error*: string
seedPhraseIndexes*: seq[int]
freePairingSlots*: int
keyUid*: string
pinRetries*: int
pukRetries*: int
eip1581Key*: KeyDetails
encryptionKey*: KeyDetails
masterKey*: KeyDetails
walletKey*: KeyDetails
walletRootKey*: KeyDetails
whisperKey*: KeyDetails
proc toKeyDetails(jsonObj: JsonNode): KeyDetails =
discard jsonObj.getProp(RequestParamAddress, result.address)
discard jsonObj.getProp(RequestParamPrivateKey, result.privateKey)
if jsonObj.getProp(RequestParamPublicKey, result.publicKey):
result.publicKey = "0x" & result.publicKey
proc toKeycardEvent(jsonObj: JsonNode): KeycardEvent =
discard jsonObj.getProp(ErrorKey, result.error)
discard jsonObj.getProp(RequestParamFreeSlots, result.freePairingSlots)
discard jsonObj.getProp(RequestParamPINRetries, result.pinRetries)
discard jsonObj.getProp(RequestParamPUKRetries, result.pukRetries)
if jsonObj.getProp(RequestParamKeyUID, result.keyUid):
result.keyUid = "0x" & result.keyUid
var obj: JsonNode
if(jsonObj.getProp(RequestParamEIP1581Key, obj)):
result.eip1581Key = toKeyDetails(obj)
if(jsonObj.getProp(RequestParamEncKey, obj)):
result.encryptionKey = toKeyDetails(obj)
if(jsonObj.getProp(RequestParamMasterKey, obj)):
result.masterKey = toKeyDetails(obj)
if(jsonObj.getProp(RequestParamWalletKey, obj)):
result.walletKey = toKeyDetails(obj)
if(jsonObj.getProp(RequestParamWalleRootKey, obj)):
result.walletRootKey = toKeyDetails(obj)
if(jsonObj.getProp(RequestParamWhisperKey, obj)):
result.whisperKey = toKeyDetails(obj)
var indexesArr: JsonNode
if jsonObj.getProp(RequestParamMnemonicIdxs, indexesArr) and indexesArr.kind == JArray:
for ind in indexesArr:
result.seedPhraseIndexes.add(ind.getInt)

View File

@ -0,0 +1,240 @@
import NimQml, json, os, chronicles, random
import keycard_go
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../constants as status_const
import constants
type FlowType {.pure.} = enum
NoFlow = -1 # this type is added only for the desktop app purpose
GetAppInfo = 0 # enumeration of these flows should follow enumeration in the `status-keycard-go`
RecoverAccount
LoadAccount
Login
ExportPublic
Sign
ChangePIN
ChangePUK
ChangePairing
UnpairThis
UnpairOthers
DeleteAccountAndUnpair
const PINLengthForStatusApp* = 6
const PUKLengthForStatusApp* = 12
const SupportedMnemonicLength12* = 12
const SupportedMnemonicLength18* = 18
const SupportedMnemonicLength24* = 24
const MnemonicLengthForStatusApp = SupportedMnemonicLength12
const TimerIntervalInMilliseconds = 3 * 1000 # 3 seconds
const SignalKeycardResponse* = "keycardResponse"
logScope:
topics = "keycard-service"
include ../../common/json_utils
include ../../common/mnemonics
include internal
include async_tasks
type
KeycardArgs* = ref object of Args
flowType*: string
flowEvent*: KeycardEvent
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
closingApp: bool
currentFlow: FlowType
#################################################
# Forward declaration section
proc runTimer(self: Service)
proc factoryReset*(self: Service)
#################################################
proc setup(self: Service) =
self.QObject.setup
proc delete*(self: Service) =
self.closingApp = true
self.QObject.delete
proc newService*(events: EventEmitter, threadpool: ThreadPool): Service =
new(result)
result.setup()
result.events = events
result.threadpool = threadpool
result.closingApp = false
result.currentFlow = FlowType.NoFlow
proc init*(self: Service) =
debug "init keycard using ", pairingsJson=status_const.KEYCARDPAIRINGDATAFILE
let initResp = keycard_go.keycardInitFlow(status_const.KEYCARDPAIRINGDATAFILE)
debug "initialization response: ", initResp
proc processSignal(self: Service, signal: string) =
var jsonSignal: JsonNode
try:
jsonSignal = signal.parseJson
except:
error "Invalid signal received", data = signal
return
debug "keycard_signal", response=signal
var typeObj, eventObj: JsonNode
if(not jsonSignal.getProp(ResponseKeyType, typeObj) or
not jsonSignal.getProp(ResponseKeyEvent, eventObj)):
return
let flowType = typeObj.getStr
let flowEvent = toKeycardEvent(eventObj)
self.events.emit(SignalKeycardResponse, KeycardArgs(flowType: flowType, flowEvent: flowEvent))
proc receiveKeycardSignal(self: Service, signal: string) {.slot.} =
self.processSignal(signal)
proc buildSeedPhrasesFromIndexes*(self: Service, seedPhraseIndexes: seq[int]): seq[string] =
var seedPhrase: seq[string]
for ind in seedPhraseIndexes:
seedPhrase.add(englishWords[ind])
return seedPhrase
proc startFlow(self: Service, payload: JsonNode) =
let response = keycard_go.keycardStartFlow(self.currentFlow.int, $payload)
debug "keycardStartFlow", flowType=self.currentFlow.int, payload=payload, response=response
proc resumeFlow(self: Service, payload: JsonNode) =
let response = keycard_go.keycardResumeFlow($payload)
debug "keycardResumeFlow", flowType=self.currentFlow.int, payload=payload, response=response
proc cancelCurrentFlow*(self: Service) =
let response = keycard_go.keycardCancelFlow()
self.currentFlow = FlowType.NoFlow
debug "keycardCancelFlow", flowType=self.currentFlow.int, response=response
proc generateRandomPUK*(self: Service): string =
for i in 0 ..< PUKLengthForStatusApp:
result = result & $rand(0 .. 9)
proc onTimeout(self: Service, response: string) {.slot.} =
if(self.closingApp or self.currentFlow == FlowType.NoFlow):
return
debug "onTimeout, about to start flow: ", flowType=self.currentFlow
self.startFlow(%* { })
proc runTimer(self: Service) =
if(self.closingApp or self.currentFlow == FlowType.NoFlow):
return
let arg = TimerTaskArg(
tptr: cast[ByteAddress](timerTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onTimeout",
timeoutInMilliseconds: TimerIntervalInMilliseconds
)
self.threadpool.start(arg)
proc startLoadAccountFlow*(self: Service, factoryReset: bool) =
var payload = %* { }
if factoryReset:
payload[RequestParamFactoryReset] = %* factoryReset
self.currentFlow = FlowType.LoadAccount
self.startFlow(payload)
proc startLoadAccountFlowWithSeedPhrase*(self: Service, seedPhraseLength: int, seedPhrase: string, factoryReset: bool) =
if seedPhrase.len == 0:
info "empty seed phrase provided"
return
var payload = %* {
RequestParamOverwrite: true,
RequestParamMnemonicLen: seedPhraseLength,
RequestParamNewPUK: self.generateRandomPUK(),
RequestParamMnemonic: seedPhrase
}
if factoryReset:
payload[RequestParamFactoryReset] = %* factoryReset
self.currentFlow = FlowType.LoadAccount
self.startFlow(payload)
proc startLoginFlow*(self: Service) =
let payload = %* { }
self.currentFlow = FlowType.Login
self.startFlow(payload)
proc startLoginFlowAutomatically*(self: Service, pin: string) =
let payload = %* {
RequestParamPIN: pin
}
self.currentFlow = FlowType.Login
self.startFlow(payload)
proc startRecoverAccountFlow*(self: Service) =
let payload = %* { }
self.currentFlow = FlowType.RecoverAccount
self.startFlow(payload)
proc storePin*(self: Service, pin: string, puk: string) =
if pin.len == 0:
info "empty pin provided"
return
var payload = %* {
RequestParamOverwrite: true,
RequestParamMnemonicLen: MnemonicLengthForStatusApp,
RequestParamPIN: pin,
RequestParamNewPIN: pin
}
if puk.len > 0:
payload[RequestParamNewPUK] = %* puk
self.resumeFlow(payload)
proc enterPin*(self: Service, pin: string) =
if pin.len == 0:
info "empty pin provided"
return
var payload = %* {
RequestParamPIN: pin
}
self.resumeFlow(payload)
proc enterPuk*(self: Service, puk: string) =
if puk.len == 0:
info "empty puk provided"
return
var payload = %* {
RequestParamPUK: puk
}
self.resumeFlow(payload)
proc storeSeedPhrase*(self: Service, seedPhraseLength: int, seedPhrase: string) =
if seedPhrase.len == 0:
info "empty seed phrase provided"
return
var payload = %* {
RequestParamOverwrite: true,
RequestParamMnemonicLen: seedPhraseLength,
RequestParamNewPUK: self.generateRandomPUK(),
RequestParamMnemonic: seedPhrase
}
self.resumeFlow(payload)
proc resumeCurrentFlow*(self: Service) =
var payload = %* { }
self.resumeFlow(payload)
proc resumeCurrentFlowLater*(self: Service) =
self.runTimer()
proc factoryReset*(self: Service) =
var payload = %* {
RequestParamFactoryReset: true
}
self.resumeFlow(payload)

View File

@ -42,21 +42,21 @@ QtObject:
signalConnect(self.keychainManager, "error(QString, int, QString)", self,
"onKeychainManagerError(QString, int, QString)", 2)
proc storePassword*(self: Service, username: string, password: string) =
self.keychainManager.storeDataAsync(username, password)
proc storeData*(self: Service, key: string, data: string) =
self.keychainManager.storeDataAsync(key, data)
proc tryToObtainPassword*(self: Service, username: string) =
self.keychainManager.readDataAsync(username)
proc tryToObtainData*(self: Service, key: string) =
self.keychainManager.readDataAsync(key)
proc onKeychainManagerError*(self: Service, errorType: string, errorCode: int,
errorDescription: string) {.slot.} =
## This slot is called in case an error occured while we're dealing with
## KeychainManager. So far we're just logging the error.
info "KeychainManager stopped: ", msg = errorCode, errorDescription
info "KeychainManager stopped: ", errCode=errorCode, errType=errorType, errDesc=errorDescription
let arg = KeyChainServiceArg(errCode: errorCode, errType: errorType,
errDescription: errorDescription)
self.events.emit("", arg)
self.events.emit(SIGNAL_KEYCHAIN_SERVICE_ERROR, arg)
proc onKeychainManagerSuccess*(self: Service, data: string) {.slot.} =
## This slot is called in case a password is successfully retrieved from the

View File

@ -218,6 +218,26 @@ proc saveAccountAndLogin*(hashedPassword: string, account, subaccounts, settings
error "error doing rpc request", methodName = "saveAccountAndLogin", exception=e.msg
raise newException(RpcException, e.msg)
proc saveAccountAndLoginWithKeycard*(chatKey, password: string, account, subaccounts, settings, config: JsonNode):
RpcResponse[JsonNode] {.raises: [Exception].} =
try:
let response = status_go.saveAccountAndLoginWithKeycard($account, password, $settings, $config, $subaccounts, chatKey)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "saveAccountAndLogin", exception=e.msg
raise newException(RpcException, e.msg)
proc convertToKeycardAccount*(keyStoreDir: string, account: JsonNode, settings: JsonNode, password: string, newPassword: string):
RpcResponse[JsonNode] {.raises: [Exception].} =
try:
let response = status_go.convertToKeycardAccount(keyStoreDir, $account, $settings, password, newPassword)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "convertToKeycardAccount", exception=e.msg
raise newException(RpcException, e.msg)
proc login*(name, keyUid, hashedPassword, thumbnail, large: string, nodeCfgObj: string):
RpcResponse[JsonNode]
{.raises: [Exception].} =
@ -238,6 +258,14 @@ proc login*(name, keyUid, hashedPassword, thumbnail, large: string, nodeCfgObj:
error "error doing rpc request", methodName = "login", exception=e.msg
raise newException(RpcException, e.msg)
proc loginWithKeycard*(chatKey, password: string, account: JsonNode): RpcResponse[JsonNode] {.raises: [Exception].} =
try:
let response = status_go.loginWithKeycard($account, password, chatKey)
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error doing rpc request", methodName = "loginWithKeycard", exception=e.msg
raise newException(RpcException, e.msg)
proc verifyAccountPassword*(address: string, password: string, keystoreDir: string):
RpcResponse[JsonNode] {.raises: [Exception].} =
try:

View File

@ -59,6 +59,7 @@ let
ROOTKEYSTOREDIR* = joinPath(baseDir, "data", "keystore")
TMPDIR* = joinPath(baseDir, "tmp") & sep
LOGDIR* = joinPath(baseDir, "logs") & sep
KEYCARDPAIRINGDATAFILE* = joinPath(baseDir, "data", "keycard/pairings.json")
proc ensureDirectories*(dataDir, tmpDir, logDir: string) =
createDir(dataDir)

View File

@ -1,6 +1,7 @@
import NimQml, chronicles, os, strformat, strutils, times, md5, json
import status_go
import keycard_go
import app/core/main
import constants
@ -11,6 +12,7 @@ logScope:
topics = "status-app"
var signalsManagerQObjPointer: pointer
var keycardServiceQObjPointer: pointer
proc isExperimental(): string =
result = if getEnv("EXPERIMENTAL") == "1": "1" else: "0" # value explicity passed to avoid trusting input
@ -66,11 +68,15 @@ proc setupRemoteSignalsHandling() =
# Please note that this must use the `cdecl` calling convention because
# it will be passed as a regular C function to statusgo_backend. This means that
# we cannot capture any local variables here (we must rely on globals)
var callback: SignalCallback = proc(p0: cstring) {.cdecl.} =
var callbackStatusGo: status_go.SignalCallback = proc(p0: cstring) {.cdecl.} =
if signalsManagerQObjPointer != nil:
signal_handler(signalsManagerQObjPointer, p0, "receiveSignal")
status_go.setSignalEventCallback(callbackStatusGo)
status_go.setSignalEventCallback(callback)
var callbackKeycardGo: keycard_go.KeycardSignalCallback = proc(p0: cstring) {.cdecl.} =
if keycardServiceQObjPointer != nil:
signal_handler(keycardServiceQObjPointer, p0, "receiveKeycardSignal")
keycard_go.setSignalEventCallback(callbackKeycardGo)
proc mainProc() =
if defined(macosx) and defined(production):
@ -133,6 +139,7 @@ proc mainProc() =
defer:
info "shutting down..."
signalsManagerQObjPointer = nil
keycardServiceQObjPointer = nil
isProductionQVariant.delete()
isExperimentalQVariant.delete()
signalsManagerQVariant.delete()
@ -149,6 +156,12 @@ proc mainProc() =
info "Terminating the app as the second instance"
quit()
# We need these global variables in order to be able to access the application
# from the non-closure callback passed to `statusgo_backend.setSignalEventCallback`
signalsManagerQObjPointer = cast[pointer](statusFoundation.signalsManager.vptr)
keycardServiceQObjPointer = cast[pointer](appController.keycardService.vptr)
setupRemoteSignalsHandling()
info fmt("Version: {DESKTOP_VERSION}")
info fmt("Commit: {GIT_COMMIT}")
info "Current date:", currentDateTime=now()
@ -156,11 +169,6 @@ proc mainProc() =
info "starting application controller..."
appController.start()
# We need this global variable in order to be able to access the application
# from the non-closure callback passed to `statusgo_backend.setSignalEventCallback`
signalsManagerQObjPointer = cast[pointer](statusFoundation.signalsManager.vptr)
setupRemoteSignalsHandling()
info "starting application..."
app.exec()

View File

@ -31,41 +31,86 @@ OnboardingBasePage {
{
return allowNotificationsViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcome)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcome)
{
return welcomeViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeNewStatusUser ||
if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeNewStatusUser ||
root.startupStore.currentStartupState.stateType === Constants.startupState.welcomeOldStatusUser ||
root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileImportSeedPhrase)
{
return keysMainViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate ||
if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreate ||
root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey)
{
return insertDetailsViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreatePassword)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreatePassword)
{
return createPasswordViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileConfirmPassword)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileConfirmPassword)
{
return confirmPasswordViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.biometrics)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.biometrics)
{
return touchIdAuthViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileEnterSeedPhrase)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileEnterSeedPhrase)
{
return seedPhraseInputViewComponent
}
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.login)
if (root.startupStore.currentStartupState.stateType === Constants.startupState.login ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardInsertKeycard ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardReadingKeycard ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardEnterPin ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardWrongKeycard ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardWrongPin ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardMaxPinRetriesReached ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardMaxPukRetriesReached ||
root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardEmpty)
{
return loginViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPluginReader ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardInsertKeycard ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardReadingKeycard)
{
return keycardInitViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardCreatePin ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardRepeatPin ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPinSet ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEnterPin ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardWrongPin)
{
return keycardPinViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardDisplaySeedPhrase)
{
return seedphraseViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEnterSeedPhraseWords)
{
return seedphraseWordsInputViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardNotEmpty ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEmpty ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardLocked ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardRecover ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPairingSlotsReached ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPinRetriesReached ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPukRetriesReached)
{
return keycardStateViewComponent
}
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEnterPuk ||
root.startupStore.currentStartupState.stateType === Constants.startupState.keycardWrongPuk)
{
return keycardPukViewComponent
}
return undefined
}
@ -87,7 +132,9 @@ OnboardingBasePage {
onAccountImportError: {
if (error === Constants.existingAccountError) {
msgDialog.title = qsTr("Keys for this account already exist")
msgDialog.text = qsTr("Keys for this account already exist and can't be added again. If you've lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase")
msgDialog.text = qsTr("Keys for this account already exist and can't be added again. If you've lost \
your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase. In \
case of Keycard try recovering using PUK or reinstall the app and try login with the Keycard option.")
} else {
msgDialog.title = qsTr("Error importing seed")
msgDialog.text = error
@ -169,4 +216,46 @@ OnboardingBasePage {
startupStore: root.startupStore
}
}
Component {
id: keycardInitViewComponent
KeycardInitView {
startupStore: root.startupStore
}
}
Component {
id: keycardPinViewComponent
KeycardPinView {
startupStore: root.startupStore
}
}
Component {
id: keycardPukViewComponent
KeycardPukView {
startupStore: root.startupStore
}
}
Component {
id: seedphraseViewComponent
SeedPhraseView {
startupStore: root.startupStore
}
}
Component {
id: seedphraseWordsInputViewComponent
SeedPhraseWordsInputView {
startupStore: root.startupStore
}
}
Component {
id: keycardStateViewComponent
KeycardStateView {
startupStore: root.startupStore
}
}
}

View File

@ -17,6 +17,7 @@ MenuItem {
property string colorId: ""
property var colorHash
property url image: ""
property bool keycardCreatedAccount: false
property StatusIconSettings iconSettings: StatusIconSettings {
name: "add"
}
@ -65,8 +66,28 @@ MenuItem {
font.pixelSize: 15
anchors.verticalCenter: parent.verticalCenter
anchors.left: userImageOrIcon.right
anchors.leftMargin: 16
anchors.right: root.keycardCreatedAccount? keycardIcon.left : parent.right
anchors.leftMargin: Style.current.padding
color: !!root.colorId ? Theme.palette.directColor1 : Theme.palette.primaryColor1
elide: Text.ElideRight
}
Loader {
id: keycardIcon
active: root.keycardCreatedAccount
sourceComponent: keycardIconComponent
anchors.rightMargin: Style.current.padding
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
Component {
id: keycardIconComponent
StatusIcon {
icon: "keycard"
height: Style.current.padding
color: Theme.palette.baseColor1
}
}
}

View File

@ -11,7 +11,6 @@ import utils 1.0 as Imports
import shared 1.0
import shared.controls 1.0
import shared.keycard 1.0
StatusModal {
property bool firstPINFieldValid: false

View File

@ -35,6 +35,10 @@ QtObject {
return root.startupModuleInst.generateImage(source, aX, aY, bX, bY)
}
function getCroppedProfileImage() {
return root.startupModuleInst.getCroppedProfileImage()
}
function setDisplayName(value) {
root.startupModuleInst.setDisplayName(value)
}
@ -51,6 +55,18 @@ QtObject {
return root.startupModuleInst.getPassword()
}
function setPin(value) {
root.startupModuleInst.setPin(value)
}
function getPin() {
return root.startupModuleInst.getPin()
}
function setPuk(value) {
root.startupModuleInst.setPuk(value)
}
function getPasswordStrengthScore(password) {
let userName = root.startupModuleInst.importedAccountAlias
return root.startupModuleInst.getPasswordStrengthScore(password, userName)
@ -63,4 +79,12 @@ QtObject {
function setSelectedLoginAccountByIndex(index) {
root.startupModuleInst.setSelectedLoginAccountByIndex(index)
}
function checkRepeatedKeycardPinWhileTyping(pin) {
return root.startupModuleInst.checkRepeatedKeycardPinWhileTyping(pin)
}
function getSeedPhrase() {
return root.startupModuleInst.getSeedPhrase()
}
}

View File

@ -36,6 +36,7 @@ Item {
}
nameInput.text = root.startupStore.getDisplayName();
nameInput.input.edit.forceActiveFocus();
userImage.image.source = root.startupStore.getCroppedProfileImage();
}
Loader {

View File

@ -1,115 +0,0 @@
import QtQuick 2.13
import shared.keycard 1.0
import "../popups"
import "../stores"
Item {
enum OnboardingFlow {
Recover,
Generate,
ImportMnemonic
}
property var onClosed: function () {}
property bool connected: false
property int flow: KeycardFlowSelectionView.OnboardingFlow.Recover
id: keycardView
Component.onCompleted: {
insertCard.open()
KeycardStore.startConnection()
}
KeycardCreatePINModal {
id: createPinModal
onSubmitBtnClicked: KeycardStore.init(pin)
onClosed: function () {
if (!createPinModal.submitted) {
keycardView.onClosed()
}
}
}
PairingModal {
id: pairingModal
onClosed: function () {
if (!pairingModal.submitted) {
keycardView.onClosed()
}
}
}
PINModal {
id: pinModal
onClosed: function () {
if (!pinModal.submitted) {
keycardView.onClosed()
}
}
}
InsertCard {
id: insertCard
onCancel: function() {
keycardView.onClosed()
}
}
// Not Refactored Yet
// Connections {
// id: connection
// target: OnboardingStore.keycardModelInst
// ignoreUnknownSignals: true
// onCardUnpaired: {
// pairingModal.open()
// }
// onCardPaired: {
// pinModal.open()
// }
// onCardAuthenticated: {
// switch (flow) {
// case OnboardingFlow.Recover: {
// KeycardStore.recoverAccount();
// break;
// }
// case OnboardingFlow.Generate: {
// break;
// }
// case OnboardingFlow.ImportMnemonic: {
// break;
// }
// }
// }
// //TODO: support the states below
// onCardPreInit: {
// createPinModal.open()
// }
// onCardFrozen: {
// keycardView.onClosed()
// }
// onCardBlocked: {
// keycardView.onClosed()
// }
// // TODO: handle these by showing an error an prompting for another card
// // later add factory reset option for the NoFreeSlots case
// onCardNoFreeSlots: {
// keycardView.onClosed()
// }
// onCardNotKeycard: {
// keycardView.onClosed()
// }
// }
}

View File

@ -0,0 +1,97 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
Component.onCompleted: {
if(root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPluginReader) {
root.startupStore.doPrimaryAction()
}
}
QtObject {
id: d
property int index: 0
property variant images : [
Style.svg("keycard/card0@2x"),
Style.svg("keycard/card1@2x"),
Style.svg("keycard/card2@2x"),
Style.svg("keycard/card3@2x")
]
}
Timer {
interval: 400
running: true
repeat: true
onTriggered: {
d.index++
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
Image {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
fillMode: Image.PreserveAspectFit
antialiasing: true
source: d.images[d.index % d.images.length]
mipmap: true
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.weight: Font.Bold
}
}
states: [
State {
name: Constants.startupState.keycardPluginReader
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPluginReader
PropertyChanges {
target: title
text: qsTrId("Plug in Keycard reader...")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
},
State {
name: Constants.startupState.keycardInsertKeycard
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardInsertKeycard
PropertyChanges {
target: title
text: qsTrId("Insert your Keycard...")
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
},
State {
name: Constants.startupState.keycardReadingKeycard
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardReadingKeycard
PropertyChanges {
target: title
text: qsTr("Reading Keycard...")
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
}
]
}

View File

@ -0,0 +1,200 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
property int remainingAttempts: parseInt(root.startupStore.startupModuleInst.keycardData, 10)
onRemainingAttemptsChanged: {
if (root.startupStore.currentStartupState.stateType === Constants.startupState.keycardWrongPin) {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
}
onStateChanged: {
if(state === Constants.startupState.keycardPinSet) {
pinInputField.setPin("123456") // we are free to set fake pin in this case
} else {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
}
Timer {
id: timer
interval: 1000
running: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPinSet
onTriggered: {
root.startupStore.doPrimaryAction()
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
Image {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
fillMode: Image.PreserveAspectFit
antialiasing: true
source: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPinSet?
Style.svg("keycard/card-success3@2x") :
Style.svg("keycard/card3@2x")
mipmap: true
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.weight: Font.Bold
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
StatusPinInput {
id: pinInputField
Layout.alignment: Qt.AlignHCenter
validator: StatusIntValidator{bottom: 0; top: 999999;}
pinLen: Constants.keycard.general.keycardPinLength
enabled: root.startupStore.currentStartupState.stateType !== Constants.startupState.keycardPinSet
onPinInputChanged: {
if(pinInput.length == 0)
return
if(root.state === Constants.startupState.keycardCreatePin ||
root.state === Constants.startupState.keycardEnterPin ||
root.state === Constants.startupState.keycardWrongPin) {
root.startupStore.setPin(pinInput)
root.startupStore.doPrimaryAction()
}
else if(root.state === Constants.startupState.keycardRepeatPin) {
let pinsMatch = root.startupStore.checkRepeatedKeycardPinWhileTyping(pinInput)
if (pinsMatch) {
info.text = qsTr("It is very important that you do not loose this PIN")
root.startupStore.doPrimaryAction()
} else {
info.text = qsTr("PINs don't match")
}
}
}
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
wrapMode: Text.WordWrap
}
StatusBaseText {
id: message
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
wrapMode: Text.WordWrap
}
}
states: [
State {
name: Constants.startupState.keycardCreatePin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardCreatePin
PropertyChanges {
target: title
text: qsTr("Create new Keycard PIN")
}
PropertyChanges {
target: info
text: qsTr("It is very important that you do not loose this PIN")
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.startupState.keycardRepeatPin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardRepeatPin
PropertyChanges {
target: title
text: qsTr("Repeat Keycard PIN")
}
PropertyChanges {
target: info
text: qsTr("It is very important that you do not loose this PIN")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.startupState.keycardPinSet
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardPinSet
PropertyChanges {
target: title
text: qsTr("Keycard PIN set")
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.startupState.keycardEnterPin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEnterPin
PropertyChanges {
target: title
text: qsTr("Enter Keycard PIN")
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: message
text: ""
}
},
State {
name: Constants.startupState.keycardWrongPin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardWrongPin
PropertyChanges {
target: title
text: qsTr("Enter Keycard PIN")
}
PropertyChanges {
target: info
text: qsTr("PIN incorrect")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: qsTr("%n attempt(s) remaining", "", d.remainingAttempts)
color: root.remainingAttempts === 1?
Theme.palette.dangerColor1 :
Theme.palette.baseColor1
}
}
]
}

View File

@ -0,0 +1,195 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
property int remainingAttempts: parseInt(root.startupStore.startupModuleInst.keycardData, 10)
Component.onCompleted: {
d.allEntriesValid = false
d.pukArray = Array(d.pukLength)
d.pukArray.fill("")
}
QtObject {
id: d
readonly property int pukLength: 12
property var pukArray: []
property bool allEntriesValid: false
readonly property int rowSpacing: Style.current.padding
function updateValidity() {
for(let i = 0; i < pukLength; ++i) {
if(pukArray[i].length !== 1) {
allEntriesValid = false
return
}
}
allEntriesValid = true
}
function submitPuk() {
let puk = d.pukArray.join("")
root.startupStore.setPuk(puk)
root.startupStore.doPrimaryAction()
}
}
Item {
anchors.top: parent.top
anchors.bottom: footerWrapper.top
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
text: qsTr("Enter PUK code to recover Keycard")
}
RowLayout {
id: rowLayout
Layout.alignment: Qt.AlignHCenter
spacing: d.rowSpacing
Component.onCompleted: {
for (var i = 0; i < children.length - 1; ++i) {
if(children[i] && children[i].input && children[i+1] && children[i+1].input){
children[i].input.tabNavItem = children[i+1].input.edit
}
}
if(children.length > 0){
children[0].input.edit.forceActiveFocus()
}
}
Repeater {
model: d.pukLength
delegate: StatusInput {
Layout.preferredWidth: Constants.keycard.general.pukCellWidth
Layout.preferredHeight: Constants.keycard.general.pukCellHeight
input.acceptReturn: true
validators: [
StatusRegularExpressionValidator {
regularExpression: /[0-9]/
errorMessage: ""
},
StatusMinLengthValidator {
minLength: 1
errorMessage: ""
}
]
onTextChanged: {
text = text.trim()
if(text.length >= 1) {
text = text.charAt(0);
}
if(Utils.isDigit(text)) {
let nextInd = index+1
if(nextInd <= rowLayout.children.length - 1 &&
rowLayout.children[nextInd] &&
rowLayout.children[nextInd].input){
rowLayout.children[nextInd].input.edit.forceActiveFocus()
}
}
else {
text = ""
}
d.pukArray[index] = text
d.updateValidity()
}
onKeyPressed: {
if(input.edit.keyEvent === Qt.Key_Backspace){
if (text == ""){
let prevInd = index-1
if(prevInd >= 0){
rowLayout.children[prevInd].input.edit.forceActiveFocus()
}
}
}
else if (input.edit.keyEvent === Qt.Key_Return ||
input.edit.keyEvent === Qt.Key_Enter) {
if(d.allEntriesValid) {
event.accepted = true
d.submitPuk()
}
}
}
}
}
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
color: Theme.palette.dangerColor1
horizontalAlignment: Qt.AlignHCenter
}
}
}
Item {
id: footerWrapper
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: Constants.keycard.general.footerWrapperHeight
StatusButton {
anchors.top: parent.top
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
enabled: d.allEntriesValid
text: qsTr("Recover Keycard")
onClicked: {
d.submitPuk()
}
}
}
states: [
State {
name: Constants.startupState.keycardEnterPuk
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEnterPuk
PropertyChanges {
target: info
text: ""
}
},
State {
name: Constants.startupState.keycardWrongPuk
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardWrongPuk
PropertyChanges {
target: info
text: qsTr("Invalid PUK code, %n attempt(s) remaining", "", root.remainingAttempts)
}
StateChangeScript {
script: d.allEntriesValid = false
}
}
]
}

View File

@ -0,0 +1,288 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
Item {
anchors.top: parent.top
anchors.bottom: footerWrapper.top
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
Image {
id: image
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
fillMode: Image.PreserveAspectFit
antialiasing: true
mipmap: true
}
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
wrapMode: Text.WordWrap
}
}
}
Item {
id: footerWrapper
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: Constants.keycard.general.footerWrapperHeight
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.current.bigPadding
StatusButton {
id: button
visible: text.length > 0
Layout.alignment: Qt.AlignHCenter
focus: true
onClicked: {
root.startupStore.doPrimaryAction()
}
}
StatusBaseText {
id: link
visible: text.length > 0
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.buttonFontSize
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
parent.font.underline = true
}
onExited: {
parent.font.underline = false
}
onClicked: {
root.startupStore.doSecondaryAction()
}
}
}
}
}
states: [
State {
name: Constants.startupState.keycardNotEmpty
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardNotEmpty
PropertyChanges {
target: image
source: Style.svg("keycard/card3@2x")
}
PropertyChanges {
target: title
text: qsTr("This Keycard already stores keys")
}
PropertyChanges {
target: info
text: qsTr("To generate new keys, you will need to perform a factory reset first")
color: Theme.palette.directColor1
}
PropertyChanges {
target: button
text: qsTr("Factory reset")
type: StatusBaseButton.Type.Normal
}
PropertyChanges {
target: link
text: qsTr("Insert another Keycard")
color: Theme.palette.primaryColor1
}
},
State {
name: Constants.startupState.keycardEmpty
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardEmpty
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: ""
}
PropertyChanges {
target: info
text: qsTr("The keycard is empty")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: button
text: qsTr("Generate new keys for this Keycard")
type: StatusBaseButton.Type.Normal
}
PropertyChanges {
target: link
text: ""
}
},
State {
name: Constants.startupState.keycardLocked
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardLocked
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: qsTr("Keycard locked and already stores keys")
}
PropertyChanges {
target: info
text: qsTr("The Keycard you have inserted is locked, you will need to factory reset it before proceeding")
color: Theme.palette.directColor1
}
PropertyChanges {
target: button
text: qsTr("Factory reset")
type: StatusBaseButton.Type.Normal
}
PropertyChanges {
target: link
text: qsTr("Insert another Keycard")
color: Theme.palette.primaryColor1
}
},
State {
name: Constants.startupState.keycardMaxPairingSlotsReached
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPairingSlotsReached
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: qsTr("Keycard locked")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: info
text: qsTr("Max pairing slots reached for this keycard")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: button
text: qsTr("Factory reset")
type: StatusBaseButton.Type.Normal
}
PropertyChanges {
target: link
text: qsTr("Insert another Keycard")
color: Theme.palette.primaryColor1
}
},
State {
name: Constants.startupState.keycardMaxPukRetriesReached
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPukRetriesReached
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: qsTr("Keycard locked")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: info
text: qsTr("Max PUK retries reached for this keycard")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: button
text: qsTr("Factory reset")
type: StatusBaseButton.Type.Normal
}
PropertyChanges {
target: link
text: qsTr("Insert another Keycard")
color: Theme.palette.primaryColor1
}
},
State {
name: Constants.startupState.keycardMaxPinRetriesReached
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardMaxPinRetriesReached
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: ""
}
PropertyChanges {
target: info
text: qsTr("Keycard locked")
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: button
text: qsTr("Recover your Keycard")
type: StatusBaseButton.Type.Danger
}
PropertyChanges {
target: link
text: ""
}
},
State {
name: Constants.startupState.keycardRecover
when: root.startupStore.currentStartupState.stateType === Constants.startupState.keycardRecover
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
}
PropertyChanges {
target: title
text: qsTr("Recover your Keycard")
}
PropertyChanges {
target: info
text: ""
}
PropertyChanges {
target: button
text: qsTr("Recover with seed phrase")
type: StatusBaseButton.Type.Danger
}
PropertyChanges {
target: link
text: qsTr("Recover with PUK")
color: Theme.palette.dangerColor1
}
}
]
}

View File

@ -86,6 +86,7 @@ Item {
id: keycardLink
Layout.alignment: Qt.AlignHCenter
color: Theme.palette.primaryColor1
font.pixelSize: 15
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
@ -148,10 +149,10 @@ Item {
//TODO remove when sync code is implemented
opacity: 0.0
}
// PropertyChanges {
// target: keycardLink
// text: qsTr("Login with Keycard")
// }
PropertyChanges {
target: keycardLink
text: qsTr("Login with Keycard")
}
PropertyChanges {
target: seedLink
text: qsTr("Enter a seed phrase")
@ -179,10 +180,10 @@ Item {
//TODO remove when sync code is implemented
opacity: 1.0
}
// PropertyChanges {
// target: keycardLink
// text: qsTr("Generate keys for a new Keycard")
// }
PropertyChanges {
target: keycardLink
text: qsTr("Generate keys for a new Keycard")
}
PropertyChanges {
target: seedLink
text: qsTr("Import a seed phrase")
@ -217,10 +218,10 @@ Item {
//TODO remove when sync code is implemented
opacity: 1.0
}
// PropertyChanges {
// target: keycardLink
// text: qsTr("Import a seed phrase into a new Keycard")
// }
PropertyChanges {
target: keycardLink
text: qsTr("Import a seed phrase into a new Keycard")
}
PropertyChanges {
target: seedLink
text: ""

View File

@ -7,7 +7,8 @@ import QtGraphicalEffects 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1 as StatusQControls
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import shared.panels 1.0
@ -27,47 +28,120 @@ Item {
property StartupStore startupStore
Component.onCompleted: {
d.resetLogin()
}
onStateChanged: {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
QtObject {
id: d
property bool loading: false
readonly property string stateLoginRegularUser: "regularUserLogin"
readonly property string stateLoginKeycardUser: "keycardUserLogin"
property int index: 0
property variant images : [
Style.svg("keycard/card0@2x"),
Style.svg("keycard/card1@2x"),
Style.svg("keycard/card2@2x"),
Style.svg("keycard/card3@2x")
]
property int remainingAttempts: parseInt(root.startupStore.startupModuleInst.keycardData, 10)
onRemainingAttemptsChanged: {
pinInputField.statesInitialization()
pinInputField.forceFocus()
}
function doLogin(password) {
if (loading || password.length === 0)
if (d.loading || password.length === 0)
return
loading = true
d.loading = true
txtPassword.textField.clear()
root.startupStore.setPassword(password)
root.startupStore.doPrimaryAction()
}
function resetLogin() {
if(localAccountSettings.storeToKeychainValue === Constants.storeToKeychainValueStore)
{
connection.enabled = true
function doKeycardLogin(pin) {
if (d.loading || pin.length === 0)
return
d.loading = true
root.startupStore.setPin(pin)
root.startupStore.doPrimaryAction()
}
else
function resetLogin() {
if(localAccountSettings.storeToKeychainValue !== Constants.keychain.storedValue.store)
{
if (!root.startupStore.selectedLoginAccount.keycardCreatedAccount){
txtPassword.visible = true
txtPassword.forceActiveFocus(Qt.MouseFocusReason)
}
}
}
}
Component.onCompleted: {
resetLogin()
Timer {
interval: 400
running: root.state === d.stateLoginKeycardUser ||
root.state === Constants.startupState.loginKeycardInsertKeycard ||
root.state === Constants.startupState.loginKeycardReadingKeycard
repeat: true
onTriggered: {
d.index++
image.source = d.images[d.index % d.images.length]
}
}
Connections{
id: connection
target: root.startupStore.startupModuleInst
onObtainingPasswordError: {
enabled = false
if (root.startupStore.selectedLoginAccount.keycardCreatedAccount) {
root.startupStore.doPrimaryAction() // in this case, switch to enter pin state
}
if (errorType === Constants.keychain.errorType.authentication) {
// We are notifying user only about keychain errors.
return
}
obtainingPasswordErrorNotification.confirmationText = errorDescription
obtainingPasswordErrorNotification.open()
}
onObtainingPasswordSuccess: {
enabled = false
doLogin(password)
if(localAccountSettings.storeToKeychainValue !== Constants.keychain.storedValue.store)
return
if (root.startupStore.selectedLoginAccount.keycardCreatedAccount) {
d.doKeycardLogin(password)
}
else {
d.doLogin(password)
}
}
onAccountLoginError: {
if (error) {
if (!root.startupStore.selectedLoginAccount.keycardCreatedAccount) {
// SQLITE_NOTADB: "file is not a database"
if (error === "file is not a database") {
txtPassword.validationError = qsTr("Password incorrect")
} else {
txtPassword.validationError = qsTr("Login failed: %1").arg(error.toUpperCase())
}
d.loading = false
txtPassword.textField.forceActiveFocus()
}
}
}
}
@ -85,59 +159,31 @@ Item {
}
}
Item {
id: element
width: 360
height: childrenRect.height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.bigPadding
Image {
id: statusIcon
width: 140
height: 140
id: image
Layout.alignment: Qt.AlignHCenter
fillMode: Image.PreserveAspectFit
source: Style.png("status-logo")
anchors.horizontalCenter: parent.horizontalCenter
antialiasing: true
mipmap: true
}
StatusBaseText {
id: welcomeBackText
text: qsTr("Welcome back")
id: title
Layout.alignment: Qt.AlignHCenter
font.weight: Font.Bold
font.pixelSize: 17
anchors.top: statusIcon.bottom
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: Constants.keycard.general.fontSize1
color: Theme.palette.directColor1
}
ConfirmAddExistingKeyModal {
id: confirmAddExstingKeyModal
onOpenModalClicked: {
root.startupStore.doTertiaryAction()
}
}
SelectAnotherAccountModal {
id: selectAnotherAccountModal
startupStore: root.startupStore
onAccountSelected: {
root.startupStore.setSelectedLoginAccountByIndex(index)
resetLogin()
}
onOpenModalClicked: {
root.startupStore.doTertiaryAction()
}
}
Item {
id: userInfo
height: userImage.height
anchors.top: welcomeBackText.bottom
anchors.topMargin: 64
width: 318
anchors.horizontalCenter: parent.horizontalCenter
Layout.alignment: Qt.AlignHCenter
UserImage {
id: userImage
@ -153,54 +199,53 @@ Item {
text: root.startupStore.selectedLoginAccount.username
font.pixelSize: 17
anchors.left: userImage.right
anchors.leftMargin: 16
anchors.right: root.startupStore.selectedLoginAccount.keycardCreatedAccount?
keycardIcon.left : changeAccountBtn.left
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: userImage.verticalCenter
color: Theme.palette.directColor1
elide: Text.ElideRight
}
StatusIcon {
id: keycardIcon
visible: root.startupStore.selectedLoginAccount.keycardCreatedAccount
anchors.right: changeAccountBtn.left
anchors.verticalCenter: userImage.verticalCenter
icon: "keycard"
height: Style.current.padding
color: Theme.palette.baseColor1
}
MouseArea {
property bool accountPopupWasOpened: false
anchors.fill: parent
anchors.margins: -10
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
// cache opened state, because it is always false in onClicked
// because of CloseOnPressOutsideParent policy of accountsPopup
accountPopupWasOpened = accountsPopup.opened
}
onClicked: {
if (!accountPopupWasOpened) {
changeAccountBtn.clicked(mouse)
}
accountPopupWasOpened = accountsPopup.opened
}
onClicked: changeAccountBtn.clicked(mouse)
}
StatusQControls.StatusFlatRoundButton {
StatusFlatRoundButton {
id: changeAccountBtn
icon.name: "chevron-down"
type: StatusQControls.StatusFlatRoundButton.Type.Tertiary
type: StatusFlatRoundButton.Type.Tertiary
width: 24
height: 24
id: changeAccountBtn
objectName: "changeAccountBtn"
anchors.verticalCenter: usernameText.verticalCenter
anchors.right: parent.right
onClicked: {
if (accountsPopup.opened) {
accountsPopup.close()
} else {
accountsPopup.popup(width-userInfo.width-16, userInfo.height+4)
accountsPopup.popup(userInfo, 0, userInfo.height+4)
}
}
}
StatusPopupMenu {
id: accountsPopup
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
width: 346
width: parent.width
dim: false
Repeater {
id: accounts
@ -210,9 +255,10 @@ Item {
image: model.thumbnailImage
colorId: model.colorId
colorHash: model.colorHash
keycardCreatedAccount: model.keycardCreatedAccount
onClicked: {
root.startupStore.setSelectedLoginAccountByIndex(index)
resetLogin()
d.resetLogin()
accountsPopup.close()
}
}
@ -236,48 +282,45 @@ Item {
}
}
}
}
Item {
id: passwordSection
Layout.fillWidth: true
Layout.preferredHeight: txtPassword.height
Layout.alignment: Qt.AlignHCenter
Input {
id: txtPassword
width: 318
height: 70
anchors.top: userInfo.bottom
anchors.topMargin: Style.current.padding * 2
anchors.left: undefined
anchors.right: undefined
anchors.horizontalCenter: parent.horizontalCenter
enabled: !loading
enabled: !d.loading
validationErrorAlignment: Text.AlignHCenter
validationErrorTopMargin: 10
placeholderText: loading ?
placeholderText: d.loading ?
qsTr("Connecting...") :
qsTr("Password")
textField.objectName: "loginPasswordInput"
textField.echoMode: TextInput.Password
Keys.onReturnPressed: {
doLogin(textField.text)
d.doLogin(textField.text)
}
onTextEdited: {
validationError = "";
loading = false
d.loading = false
}
}
StatusQControls.StatusRoundButton {
StatusRoundButton {
id: submitBtn
width: 40
height: 40
type: StatusQControls.StatusRoundButton.Type.Secondary
type: StatusRoundButton.Type.Secondary
icon.name: "arrow-right"
opacity: (loading || txtPassword.text.length > 0) ? 1 : 0
anchors.top: userInfo.bottom
anchors.topMargin: 34
opacity: (d.loading || txtPassword.text.length > 0) ? 1 : 0
anchors.left: txtPassword.right
anchors.leftMargin: (loading || txtPassword.text.length > 0) ? Style.current.padding : Style.current.smallPadding
state: loading ? "pending" : "default"
anchors.leftMargin: (d.loading || txtPassword.text.length > 0) ? Style.current.padding : Style.current.smallPadding
state: d.loading ? "pending" : "default"
onClicked: {
doLogin(txtPassword.textField.text)
d.doLogin(txtPassword.textField.text)
}
// https://www.figma.com/file/BTS422M9AkvWjfRrXED3WC/%F0%9F%91%8B-Onboarding%E2%8E%9CDesktop?node-id=6%3A0
@ -293,21 +336,538 @@ Item {
}
}
}
}
Connections {
target: root.startupStore.startupModuleInst
onAccountLoginError: {
if (error) {
// SQLITE_NOTADB: "file is not a database"
if (error === "file is not a database") {
txtPassword.validationError = qsTr("Password incorrect")
} else {
txtPassword.validationError = qsTr("Login failed: %1").arg(error.toUpperCase())
Column {
id: pinSection
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.current.padding
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: Constants.keycard.general.fontSize2
wrapMode: Text.WordWrap
text: qsTr("Enter Keycard PIN")
}
loading = false
txtPassword.textField.forceActiveFocus()
StatusPinInput {
id: pinInputField
anchors.horizontalCenter: parent.horizontalCenter
validator: StatusIntValidator{bottom: 0; top: 999999;}
pinLen: Constants.keycard.general.keycardPinLength
enabled: !d.loading
onPinInputChanged: {
if(pinInput.length == 0)
return
root.startupStore.setPin(pinInput)
root.startupStore.doPrimaryAction()
}
}
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
wrapMode: Text.WordWrap
}
StatusBaseText {
id: message
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
wrapMode: Text.WordWrap
}
StatusButton {
id: button
Layout.alignment: Qt.AlignHCenter
focus: true
onClicked: {
root.startupStore.doPrimaryAction()
}
}
StatusBaseText {
id: link
Layout.alignment: Qt.AlignHCenter
color: Theme.palette.primaryColor1
font.pixelSize: Constants.keycard.general.fontSize2
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
parent.font.underline = true
}
onExited: {
parent.font.underline = false
}
onClicked: {
root.startupStore.doPrimaryAction()
}
}
}
}
states: [
State {
name: d.stateLoginRegularUser
when: !root.startupStore.selectedLoginAccount.keycardCreatedAccount
PropertyChanges {
target: image
source: Style.png("status-logo")
Layout.preferredHeight: 140
Layout.preferredWidth: 140
}
PropertyChanges {
target: title
text: qsTr("Welcome back")
}
PropertyChanges {
target: passwordSection
visible: true
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: ""
visible: false
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: d.stateLoginKeycardUser
when: root.startupStore.selectedLoginAccount.keycardCreatedAccount &&
root.startupStore.currentStartupState.stateType === Constants.startupState.login
PropertyChanges {
target: image
source: Style.svg("keycard/card3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Plug in Keycard reader...")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardInsertKeycard
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardInsertKeycard
PropertyChanges {
target: image
source: Style.svg("keycard/card3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Insert your Keycard...")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardReadingKeycard
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardReadingKeycard
PropertyChanges {
target: image
source: Style.svg("keycard/card3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Reading Keycard...")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardEnterPin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardEnterPin
PropertyChanges {
target: image
source: Style.svg("keycard/card3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: true
}
PropertyChanges {
target: info
text: ""
visible: false
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardWrongKeycard
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardWrongKeycard
PropertyChanges {
target: image
source: Style.svg("keycard/card-wrong3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Wrong Keycard!\nThe card inserted is not linked to your profile.")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: qsTr("Insert another Keycard")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.baseColor1
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardWrongPin
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardWrongPin
PropertyChanges {
target: image
source: Style.svg("keycard/card-wrong3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: true
}
PropertyChanges {
target: info
text: qsTr("PIN incorrect")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
text: qsTr("%n attempt(s) remaining", "", d.remainingAttempts)
color: d.remainingAttempts === 1?
Theme.palette.dangerColor1 :
Theme.palette.baseColor1
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardMaxPinRetriesReached
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardMaxPinRetriesReached
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Keycard locked")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: qsTr("Recover your Keycard")
visible: true
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardMaxPukRetriesReached
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardMaxPukRetriesReached
PropertyChanges {
target: image
source: Style.svg("keycard/card-error3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("Keycard locked")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: qsTr("Recover with seed phrase")
visible: true
}
PropertyChanges {
target: link
text: ""
visible: false
}
},
State {
name: Constants.startupState.loginKeycardEmpty
when: root.startupStore.currentStartupState.stateType === Constants.startupState.loginKeycardEmpty
PropertyChanges {
target: image
source: Style.svg("keycard/card-wrong3@2x")
Layout.preferredHeight: sourceSize.height
Layout.preferredWidth: sourceSize.width
}
PropertyChanges {
target: title
text: ""
visible: false
}
PropertyChanges {
target: passwordSection
visible: false
}
PropertyChanges {
target: pinSection
visible: false
}
PropertyChanges {
target: info
text: qsTr("The card inserted is empty")
visible: true
font.pixelSize: Constants.keycard.general.fontSize2
color: Theme.palette.dangerColor1
}
PropertyChanges {
target: message
text: ""
visible: false
}
PropertyChanges {
target: button
text: ""
visible: false
}
PropertyChanges {
target: link
text: qsTr("Generate keys for a new Keycard")
visible: true
}
}
]
}

View File

@ -277,9 +277,22 @@ Item {
function checkMnemonicLength() {
submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex])
}
text: root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase?
qsTr("Import") :
qsTr("Restore Status Profile")
text: {
if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) {
return qsTr("Import")
}
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserImportSeedPhrase) {
return qsTr("Restore Status Profile")
}
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserKeycardImport ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.appLogin) {
return qsTr("Recover Keycard")
}
else if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard) {
return qsTr("Next")
}
return ""
}
onClicked: {
let mnemonicString = "";
var sortTable = mnemonicInput.sort(function (a, b) {

View File

@ -0,0 +1,128 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
Component.onCompleted: {
let seedPhrase = root.startupStore.getSeedPhrase()
if(seedPhrase.length === 0)
return
d.seedPhraseModel = seedPhrase.split(" ")
}
QtObject {
id: d
property var seedPhraseModel: []
readonly property int numOfColumns: 4
readonly property int rowSpacing: Style.current.bigPadding
readonly property int columnSpacing: Style.current.bigPadding
}
Keys.onPressed: {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
event.accepted = true
root.startupStore.doPrimaryAction()
}
}
Item {
anchors.top: parent.top
anchors.bottom: footerWrapper.top
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
text: qsTr("Write down your seed phrase")
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
color: Theme.palette.dangerColor1
horizontalAlignment: Qt.AlignHCenter
text: qsTr("You will need this to recover your Keycard if you loose\nyour PIN of if the wrong PIN is entered five times in a row.")
}
GridLayout {
id: grid
Layout.alignment: Qt.AlignHCenter
columns: d.numOfColumns
rowSpacing: d.rowSpacing
columnSpacing: d.columnSpacing
Repeater {
model: d.seedPhraseModel
delegate: Item {
Layout.preferredWidth: Constants.keycard.general.seedPhraseCellWidth
Layout.preferredHeight: Constants.keycard.general.seedPhraseCellHeight
StatusBaseText {
id: wordNumber
width: Constants.keycard.general.seedPhraseCellNumberWidth
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Qt.AlignRight
font.pixelSize: Constants.keycard.general.seedPhraseCellFontSize
color: Theme.palette.directColor1
text: "%1.".arg(model.index + 1)
}
StatusBaseText {
id: word
anchors.left: wordNumber.right
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.current.xlPadding
horizontalAlignment: Qt.AlignLeft
font.pixelSize: Constants.keycard.general.seedPhraseCellFontSize
color: Theme.palette.directColor1
text: model.modelData
}
}
}
}
}
}
Item {
id: footerWrapper
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: Constants.keycard.general.footerWrapperHeight
StatusButton {
anchors.top: parent.top
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Next")
focus: true
onClicked: {
root.startupStore.doPrimaryAction()
}
}
}
}

View File

@ -0,0 +1,187 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import utils 1.0
import "../stores"
Item {
id: root
property StartupStore startupStore
Component.onCompleted: {
d.dirtyWordAtIndex = [false, false, false, false]
d.validWordAtIndex = [false, false, false, false]
d.anyInputDirty = false
d.allEntriesValid = false
let seedPhrase = root.startupStore.getSeedPhrase()
if(seedPhrase.length === 0)
return
d.seedPhrases = seedPhrase.split(" ")
let numbers = []
while (numbers.length < 4) {
let randomNo = Math.floor(Math.random() * 12)
if(numbers.indexOf(randomNo) == -1)
numbers.push(randomNo)
}
numbers.sort((a, b) => { return a < b? -1 : a > b? 1 : 0 })
d.wordNumbers = numbers
}
QtObject {
id: d
property var seedPhrases: []
property var wordNumbers: []
property var dirtyWordAtIndex: [false, false, false, false]
property var validWordAtIndex: [false, false, false, false]
property bool anyInputDirty: false
property bool allEntriesValid: false
readonly property int numOfColumns: 4
readonly property int rowSpacing: Style.current.bigPadding
readonly property int columnSpacing: Style.current.bigPadding
function updateValidity(index, valid, dirty) {
dirtyWordAtIndex[index] = dirty
validWordAtIndex[index] = valid
let anyDirty = false
let allValid = true
for(let i = 0; i < validWordAtIndex.length; ++i) {
allValid = allValid && validWordAtIndex[i]
anyDirty = anyDirty || (dirtyWordAtIndex[index] && !validWordAtIndex[index])
}
allEntriesValid = allValid
anyInputDirty = anyDirty
}
}
Item {
anchors.top: parent.top
anchors.bottom: footerWrapper.top
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
anchors.centerIn: parent
spacing: Style.current.padding
StatusBaseText {
id: title
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize1
font.weight: Font.Bold
color: Theme.palette.directColor1
text: qsTr("Enter seed phrase words")
}
GridLayout {
id: grid
Layout.alignment: Qt.AlignHCenter
columns: d.numOfColumns
rowSpacing: d.rowSpacing
columnSpacing: d.columnSpacing
Component.onCompleted: {
for (var i = 0; i < children.length - 1; ++i) {
if(children[i].inputField && children[i+1].inputField){
children[i].inputField.input.tabNavItem = children[i+1].inputField.input.edit
}
}
}
Repeater {
model: d.wordNumbers
delegate: Item {
Layout.preferredWidth: Constants.keycard.general.seedPhraseCellWidth
Layout.preferredHeight: Constants.keycard.general.seedPhraseCellHeight
property alias inputField: word
property alias wN: wordNumber
StatusBaseText {
id: wordNumber
width: Constants.keycard.general.seedPhraseCellNumberWidth
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Qt.AlignRight
font.pixelSize: Constants.keycard.general.seedPhraseCellFontSize
color: Theme.palette.directColor1
text: "%1.".arg(model.modelData + 1)
}
StatusInput {
id: word
anchors.left: wordNumber.right
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.current.xlPadding
input.edit.font.pixelSize: Constants.keycard.general.seedPhraseCellFontSize
input.acceptReturn: true
onTextChanged: {
if(text.length == 0)
return
if(/(^\s|^\r|^\n)|(\s$|^\r$|^\n$)/.test(text)) {
text = text.trim()
return
}
else if(/\s|\r|\n/.test(text)) {
text = ""
return
}
valid = d.seedPhrases[model.modelData] === text
d.updateValidity(index, valid, text !== "")
}
onKeyPressed: {
if (d.allEntriesValid &&
(input.edit.keyEvent === Qt.Key_Return ||
input.edit.keyEvent === Qt.Key_Enter)) {
event.accepted = true
root.startupStore.doPrimaryAction()
}
}
}
}
}
}
StatusBaseText {
id: info
Layout.alignment: Qt.AlignHCenter
font.pixelSize: Constants.keycard.general.fontSize3
color: Theme.palette.dangerColor1
horizontalAlignment: Qt.AlignHCenter
text: d.anyInputDirty && !d.allEntriesValid? qsTr("Invalid word") : ""
}
}
}
Item {
id: footerWrapper
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: Constants.keycard.general.footerWrapperHeight
StatusButton {
anchors.top: parent.top
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
enabled: d.allEntriesValid
text: qsTr("Next")
onClicked: {
root.startupStore.doPrimaryAction()
}
}
}
}

View File

@ -62,7 +62,11 @@ Item {
anchors.top: txtTitle.bottom
anchors.topMargin: Style.current.padding
color: Style.current.secondaryText
text: qsTr("Would you like to use Touch ID\nto login to Status?")
text: root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserNewKeycardKeys ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserKeycardImport?
qsTr("Would you like to use TouchID instead of a PIN code\nto login to Status using your Keycard?") :
qsTr("Would you like to use Touch ID\nto login to Status?")
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: 15
@ -86,7 +90,11 @@ Item {
objectName: "touchIdIPreferToUseMyPasswordText"
Layout.alignment: Qt.AlignHCenter
color: Theme.palette.primaryColor1
text: qsTr("I prefer to use my password")
text: root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserNewKeycardKeys ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhraseIntoKeycard ||
root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunOldUserKeycardImport?
qsTr("I prefer to use my PIN") :
qsTr("I prefer to use my password")
font.pixelSize: 15
MouseArea {
anchors.fill: parent

View File

@ -25,7 +25,7 @@ StatusModal {
function onChangePasswordResponse(success, errorMsg) {
if (success) {
if (Qt.platform.os === "osx") {
localAccountSettings.storeToKeychainValue = Constants.storeToKeychainValueStore;
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.store;
root.privacyStore.storeToKeyChain(d.passwordProcessing);
}
passwordChanged()

View File

@ -31,7 +31,6 @@ QtObject {
readonly property string nodeManagement: "nodeManagement"
readonly property string onlineUsers: "onlineUsers"
readonly property string gifWidget: "gifWidget"
readonly property string keycard: "keycard"
readonly property string communityHistoryArchiveSupport: "communityHistoryArchiveSupport"
readonly property string communitiesPortal: "communitiesPortal"
}
@ -131,9 +130,5 @@ QtObject {
else if (feature === experimentalFeatures.gifWidget) {
localAccountSensitiveSettings.isGifWidgetEnabled = !localAccountSensitiveSettings.isGifWidgetEnabled
}
else if (feature === experimentalFeatures.keycard) {
localAccountSettings.isKeycardEnabled = !localAccountSettings.isKeycardEnabled
}
}
}

View File

@ -163,18 +163,6 @@ SettingsContentBase {
}
}
// TODO: replace with StatusQ component
StatusSettingsLineButton {
anchors.leftMargin: 0
anchors.rightMargin: 0
text: qsTr("Keycard")
isSwitch: true
switchChecked: localAccountSettings.isKeycardEnabled
onClicked: {
root.advancedStore.toggleExperimentalFeature(root.advancedStore.experimentalFeatures.keycard)
}
}
StatusSectionHeadline {
anchors.left: parent.left
anchors.right: parent.right

View File

@ -41,7 +41,7 @@ ColumnLayout {
if (biometricsSwitch.checked)
Global.openPopup(storePasswordModal)
else
localAccountSettings.storeToKeychainValue = Constants.storeToKeychainValueNever;
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.never;
}
function offerToStorePassword(password, runStoreToKeyChainPopup)
@ -49,7 +49,7 @@ ColumnLayout {
if (Qt.platform.os !== "osx")
return;
localAccountSettings.storeToKeychainValue = Constants.storeToKeychainValueStore;
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.store;
root.privacyStore.storeToKeyChain(password);
}
@ -89,7 +89,7 @@ ColumnLayout {
components: [ StatusSwitch {
id: biometricsSwitch
horizontalPadding: 0
readonly property bool currentStoredValue: localAccountSettings.storeToKeychainValue === Constants.storeToKeychainValueStore
readonly property bool currentStoredValue: localAccountSettings.storeToKeychainValue === Constants.keychain.storedValue.store
checked: currentStoredValue
} ]
sensor.onClicked: biometricsSwitch.toggle()

View File

@ -0,0 +1,24 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#FF2D55" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#FF2D55"/>
</g>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<path d="M108.499 88.0308C108.225 87.9967 108 87.7761 108 87.5L108 84C108 81.7909 106.209 80 104 80C101.791 80 100 81.7909 100 84L100 87.5C100 87.7761 99.775 87.9967 99.501 88.0308C97.5274 88.2764 96 89.9598 96 92L96 96C96 98.2091 97.7909 100 100 100L108 100C110.209 100 112 98.2091 112 96L112 92C112 89.9598 110.473 88.2764 108.499 88.0308ZM110.5 92C110.5 90.6193 109.381 89.5 108 89.5L100 89.5C98.6193 89.5 97.5 90.6193 97.5 92L97.5 96C97.5 97.3807 98.6193 98.5 100 98.5L108 98.5C109.381 98.5 110.5 97.3807 110.5 96L110.5 92ZM106 88C106.276 88 106.5 87.7761 106.5 87.5L106.5 84C106.5 82.6193 105.381 81.5 104 81.5C102.619 81.5 101.5 82.6193 101.5 84L101.5 87.5C101.5 87.7761 101.724 88 102 88L106 88ZM104.984 94.1319C104.85 94.2491 104.75 94.4092 104.75 94.5877L104.75 96C104.75 96.4142 104.414 96.75 104 96.75C103.586 96.75 103.25 96.4142 103.25 96L103.25 94.5877C103.25 94.4092 103.15 94.2491 103.016 94.1319C102.7 93.8569 102.5 93.4518 102.5 93C102.5 92.1716 103.172 91.5 104 91.5C104.828 91.5 105.5 92.1716 105.5 93C105.5 93.4518 105.3 93.8569 104.984 94.1319Z" fill="black"/>
<path d="M108.499 88.0308C108.225 87.9967 108 87.7761 108 87.5L108 84C108 81.7909 106.209 80 104 80C101.791 80 100 81.7909 100 84L100 87.5C100 87.7761 99.775 87.9967 99.501 88.0308C97.5274 88.2764 96 89.9598 96 92L96 96C96 98.2091 97.7909 100 100 100L108 100C110.209 100 112 98.2091 112 96L112 92C112 89.9598 110.473 88.2764 108.499 88.0308ZM110.5 92C110.5 90.6193 109.381 89.5 108 89.5L100 89.5C98.6193 89.5 97.5 90.6193 97.5 92L97.5 96C97.5 97.3807 98.6193 98.5 100 98.5L108 98.5C109.381 98.5 110.5 97.3807 110.5 96L110.5 92ZM106 88C106.276 88 106.5 87.7761 106.5 87.5L106.5 84C106.5 82.6193 105.381 81.5 104 81.5C102.619 81.5 101.5 82.6193 101.5 84L101.5 87.5C101.5 87.7761 101.724 88 102 88L106 88ZM104.984 94.1319C104.85 94.2491 104.75 94.4092 104.75 94.5877L104.75 96C104.75 96.4142 104.414 96.75 104 96.75C103.586 96.75 103.25 96.4142 103.25 96L103.25 94.5877C103.25 94.4092 103.15 94.2491 103.016 94.1319C102.7 93.8569 102.5 93.4518 102.5 93C102.5 92.1716 103.172 91.5 104 91.5C104.828 91.5 105.5 92.1716 105.5 93C105.5 93.4518 105.3 93.8569 104.984 94.1319Z" stroke="black"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,24 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#FF2D55" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#FF2D55"/>
</g>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<path d="M108.499 88.0308C108.225 87.9967 108 87.7761 108 87.5L108 84C108 81.7909 106.209 80 104 80C101.791 80 100 81.7909 100 84L100 87.5C100 87.7761 99.775 87.9967 99.501 88.0308C97.5274 88.2764 96 89.9598 96 92L96 96C96 98.2091 97.7909 100 100 100L108 100C110.209 100 112 98.2091 112 96L112 92C112 89.9598 110.473 88.2764 108.499 88.0308ZM110.5 92C110.5 90.6193 109.381 89.5 108 89.5L100 89.5C98.6193 89.5 97.5 90.6193 97.5 92L97.5 96C97.5 97.3807 98.6193 98.5 100 98.5L108 98.5C109.381 98.5 110.5 97.3807 110.5 96L110.5 92ZM106 88C106.276 88 106.5 87.7761 106.5 87.5L106.5 84C106.5 82.6193 105.381 81.5 104 81.5C102.619 81.5 101.5 82.6193 101.5 84L101.5 87.5C101.5 87.7761 101.724 88 102 88L106 88ZM104.984 94.1319C104.85 94.2491 104.75 94.4092 104.75 94.5877L104.75 96C104.75 96.4142 104.414 96.75 104 96.75C103.586 96.75 103.25 96.4142 103.25 96L103.25 94.5877C103.25 94.4092 103.15 94.2491 103.016 94.1319C102.7 93.8569 102.5 93.4518 102.5 93C102.5 92.1716 103.172 91.5 104 91.5C104.828 91.5 105.5 92.1716 105.5 93C105.5 93.4518 105.3 93.8569 104.984 94.1319Z" fill="black"/>
<path d="M108.499 88.0308C108.225 87.9967 108 87.7761 108 87.5L108 84C108 81.7909 106.209 80 104 80C101.791 80 100 81.7909 100 84L100 87.5C100 87.7761 99.775 87.9967 99.501 88.0308C97.5274 88.2764 96 89.9598 96 92L96 96C96 98.2091 97.7909 100 100 100L108 100C110.209 100 112 98.2091 112 96L112 92C112 89.9598 110.473 88.2764 108.499 88.0308ZM110.5 92C110.5 90.6193 109.381 89.5 108 89.5L100 89.5C98.6193 89.5 97.5 90.6193 97.5 92L97.5 96C97.5 97.3807 98.6193 98.5 100 98.5L108 98.5C109.381 98.5 110.5 97.3807 110.5 96L110.5 92ZM106 88C106.276 88 106.5 87.7761 106.5 87.5L106.5 84C106.5 82.6193 105.381 81.5 104 81.5C102.619 81.5 101.5 82.6193 101.5 84L101.5 87.5C101.5 87.7761 101.724 88 102 88L106 88ZM104.984 94.1319C104.85 94.2491 104.75 94.4092 104.75 94.5877L104.75 96C104.75 96.4142 104.414 96.75 104 96.75C103.586 96.75 103.25 96.4142 103.25 96L103.25 94.5877C103.25 94.4092 103.15 94.2491 103.016 94.1319C102.7 93.8569 102.5 93.4518 102.5 93C102.5 92.1716 103.172 91.5 104 91.5C104.828 91.5 105.5 92.1716 105.5 93C105.5 93.4518 105.3 93.8569 104.984 94.1319Z" stroke="black"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,23 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#27D8B9"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="white"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,23 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#27D8B9"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="white"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,21 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="90" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="100" cy="100" r="63" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="100" cy="100" r="36" fill="#FF2D55" fill-opacity="0.1"/>
<g filter="url(#filter0_d_11634_22687)">
<path d="M60 81.8889C60 76.9797 63.9797 73 68.8889 73H131.111C136.02 73 140 76.9797 140 81.8889V117.444C140 122.354 136.02 126.333 131.111 126.333H68.8889C63.9797 126.333 60 122.354 60 117.444V81.8889Z" fill="black"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M111.274 99.4963C108 98.8057 105.555 95.8626 105.555 92.393C105.555 88.4189 108.761 85.2227 112.748 85.2227C116.734 85.2227 119.941 88.4189 119.941 92.393C120.015 95.8031 117.6 98.7046 114.394 99.4588V106.647H114.567L118.38 102.155H122.02L117.514 107.338L122.28 114.335H118.9L115.434 109.324L114.394 110.534V114.335H111.274V99.4963ZM108.415 92.393C108.415 90.0606 110.321 88.0733 112.748 88.0733C115.174 88.0733 117.168 90.0601 117.081 92.393C117.081 94.7248 115.174 96.712 112.747 96.712C110.408 96.712 108.415 94.8114 108.415 92.393Z" fill="#FF2D55"/>
<path d="M73.334 95.2222C73.334 93.9949 74.3289 93 75.5562 93H82.2229C83.4502 93 84.4451 93.9949 84.4451 95.2222V98.5556C84.4451 99.7829 83.4502 100.778 82.2229 100.778H75.5562C74.3289 100.778 73.334 99.7829 73.334 98.5556V95.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_11634_22687" x="44" y="59" width="112" height="85.332" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11634_22687"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_11634_22687" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,21 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="90" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="100" cy="100" r="63" fill="#FF2D55" fill-opacity="0.1"/>
<circle cx="100" cy="100" r="36" fill="#FF2D55" fill-opacity="0.1"/>
<g filter="url(#filter0_d_11634_22687)">
<path d="M60 81.8889C60 76.9797 63.9797 73 68.8889 73H131.111C136.02 73 140 76.9797 140 81.8889V117.444C140 122.354 136.02 126.333 131.111 126.333H68.8889C63.9797 126.333 60 122.354 60 117.444V81.8889Z" fill="black"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M111.274 99.4963C108 98.8057 105.555 95.8626 105.555 92.393C105.555 88.4189 108.761 85.2227 112.748 85.2227C116.734 85.2227 119.941 88.4189 119.941 92.393C120.015 95.8031 117.6 98.7046 114.394 99.4588V106.647H114.567L118.38 102.155H122.02L117.514 107.338L122.28 114.335H118.9L115.434 109.324L114.394 110.534V114.335H111.274V99.4963ZM108.415 92.393C108.415 90.0606 110.321 88.0733 112.748 88.0733C115.174 88.0733 117.168 90.0601 117.081 92.393C117.081 94.7248 115.174 96.712 112.747 96.712C110.408 96.712 108.415 94.8114 108.415 92.393Z" fill="#FF2D55"/>
<path d="M73.334 95.2222C73.334 93.9949 74.3289 93 75.5562 93H82.2229C83.4502 93 84.4451 93.9949 84.4451 95.2222V98.5556C84.4451 99.7829 83.4502 100.778 82.2229 100.778H75.5562C74.3289 100.778 73.334 99.7829 73.334 98.5556V95.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_11634_22687" x="44" y="59" width="112" height="85.332" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11634_22687"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_11634_22687" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,20 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,20 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,21 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,21 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,22 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,22 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,23 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,23 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" fill="#E5E5E5"/>
<rect x="-526" y="-222" width="1232" height="770" rx="5" fill="white"/>
<circle cx="90" cy="90" r="90" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="63" fill="#4360DF" fill-opacity="0.1"/>
<circle cx="90" cy="90" r="36" fill="#4360DF" fill-opacity="0.1"/>
<g filter="url(#filter0_d_1023_298171)">
<path d="M50 71.8889C50 66.9797 53.9797 63 58.8889 63H121.111C126.02 63 130 66.9797 130 71.8889V107.444C130 112.354 126.02 116.333 121.111 116.333H58.8889C53.9797 116.333 50 112.354 50 107.444V71.8889Z" fill="#2D2D2D"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.274 89.4963C97.9997 88.8057 95.5547 85.8626 95.5547 82.393C95.5547 78.4189 98.7613 75.2227 102.748 75.2227C106.734 75.2227 109.941 78.4189 109.941 82.393C110.015 85.8031 107.6 88.7046 104.394 89.4588V96.6465H104.567L108.38 92.1547H112.02L107.514 97.3382L112.28 104.335H108.9L105.434 99.3244L104.394 100.534V104.335H101.274V89.4963ZM98.4147 82.393C98.4147 80.0606 100.321 78.0733 102.748 78.0733C105.174 78.0733 107.168 80.0601 107.081 82.393C107.081 84.7248 105.174 86.712 102.747 86.712C100.408 86.712 98.4147 84.8114 98.4147 82.393Z" fill="#27D8B9"/>
<path d="M63.334 85.2222C63.334 83.9949 64.3289 83 65.5562 83H72.2229C73.4502 83 74.4451 83.9949 74.4451 85.2222V88.5556C74.4451 89.7829 73.4502 90.7778 72.2229 90.7778H65.5562C64.3289 90.7778 63.334 89.7829 63.334 88.5556V85.2222Z" fill="#F0CC73"/>
<defs>
<filter id="filter0_d_1023_298171" x="34" y="49" width="112" height="85.334" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="8"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0333333 0 0 0 0 0.1 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1023_298171"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1023_298171" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,72 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
StatusModal {
property var onCancel: function() {}
id: insertCard
anchors.centerIn: parent
showHeader: false
focus: visible
contentItem: Item {
width: insertCard.width
implicitHeight: childrenRect.height
Column {
width: parent.width - 32
anchors.horizontalCenter: parent.horizontalCenter
Item {
width: parent.width
height: 16
}
StatusBaseText {
text: qsTr("Please insert your Keycard to proceed or press the cancel button to cancel the operation")
font.pixelSize: 15
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
color: Theme.palette.directColor1
}
Item {
width: parent.width
height: 16
}
}
}
rightButtons: [
StatusButton {
id: cancelButton
text: qsTr("Cancel")
onClicked: {
insertCard.close()
onCancel()
}
}
]
// Not Refactored Yet
// Connections {
// id: connection
// target: keycardModel
// ignoreUnknownSignals: true
// onCardConnected: {
// insertCard.close()
// }
// onCardDisconnected: {
// insertCard.open()
// }
// }
}

Some files were not shown because too many files have changed in this diff Show More