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"] [submodule "vendor/nim-keycard-go"]
path = vendor/nim-keycard-go path = vendor/nim-keycard-go
url = https://github.com/status-im/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-macos \
run-windows \ run-windows \
status-go \ status-go \
keycard-go \ status-keycard-go \
update update
ifeq ($(NIM_PARAMS),) ifeq ($(NIM_PARAMS),)
@ -223,15 +223,15 @@ $(STATUSGO): | deps
$(MAKE) statusgo-shared-library $(HANDLE_OUTPUT) $(MAKE) statusgo-shared-library $(HANDLE_OUTPUT)
KEYCARDGO := vendor/nim-keycard-go/go/keycard/build/libkeycard/libkeycard.$(LIBSTATUS_EXT) STATUSKEYCARDGO := vendor/status-keycard-go/build/libkeycard/libkeycard.$(LIBSTATUS_EXT)
KEYCARDGO_LIBDIR := $(shell pwd)/$(shell dirname "$(KEYCARDGO)") STATUSKEYCARDGO_LIBDIR := $(shell pwd)/$(shell dirname "$(STATUSKEYCARDGO)")
export KEYCARDGO_LIBDIR export STATUSKEYCARDGO_LIBDIR
keycard-go: $(KEYCARDGO) status-keycard-go: $(STATUSKEYCARDGO)
$(KEYCARDGO): | deps $(STATUSKEYCARDGO): | deps
echo -e $(BUILD_MSG) "keycard-go" echo -e $(BUILD_MSG) "status-keycard-go"
+ cd vendor/nim-keycard-go && \ + cd vendor/status-keycard-go && \
$(MAKE) build-keycard-go $(HANDLE_OUTPUT) $(MAKE) build-lib $(HANDLE_OUTPUT)
QRCODEGEN := vendor/QR-Code-generator/c/libqrcodegen.a QRCODEGEN := vendor/QR-Code-generator/c/libqrcodegen.a
@ -331,9 +331,9 @@ else
endif endif
$(NIM_STATUS_CLIENT): NIM_PARAMS += $(RESOURCES_LAYOUT) $(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) "$@" && \ 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 ]] && \ [[ $$? = 0 ]] && \
(([[ $(detected_OS) = Darwin ]] && \ (([[ $(detected_OS) = Darwin ]] && \
install_name_tool -change \ 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 -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 tmp/linux/dist/usr/lib/
cp vendor/status-go/build/bin/libstatus.so.0 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" 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 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 cp bin/nim_windows_launcher.exe $(OUTPUT)/Status.exe
rcedit $(OUTPUT)/bin/Status.exe --set-icon $(OUTPUT)/resources/status.ico rcedit $(OUTPUT)/bin/Status.exe --set-icon $(OUTPUT)/resources/status.ico
rcedit $(OUTPUT)/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 libgcc_s_seh-1.dll)" $(OUTPUT)/bin/
cp "$(shell which libwinpthread-1.dll)" $(OUTPUT)/bin/ cp "$(shell which libwinpthread-1.dll)" $(OUTPUT)/bin/
echo -e $(BUILD_MSG) "deployable folder" 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) zip-windows: check-pkg-target-windows $(STATUS_CLIENT_7Z)
clean: | clean-common 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 + $(MAKE) -C vendor/DOtherSide/build --no-print-directory clean
force-rebuild-status-go: force-rebuild-status-go:
@ -594,7 +594,7 @@ $(ICON_TOOL):
run-linux: nim_status_client run-linux: nim_status_client
echo -e "\e[92mRunning:\e[39m bin/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 ./bin/nim_status_client
run-macos: nim_status_client $(ICON_TOOL) 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) run-windows: nim_status_client $(NIM_WINDOWS_PREBUILT_DLLS)
echo -e "\e[92mRunning:\e[39m bin/nim_status_client.exe" 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 ./bin/nim_status_client.exe
endif # "variables.mk" was not included 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 # note: macdeployqt rewrites rpath appropriately when building the .app bundle
switch("passL", "-rpath" & " " & getEnv("QT5_LIBDIR")) switch("passL", "-rpath" & " " & getEnv("QT5_LIBDIR"))
switch("passL", "-rpath" & " " & getEnv("STATUSGO_LIBDIR")) switch("passL", "-rpath" & " " & getEnv("STATUSGO_LIBDIR"))
switch("passL", "-rpath" & " " & getEnv("KEYCARDGO_LIBDIR")) switch("passL", "-rpath" & " " & getEnv("STATUSKEYCARDGO_LIBDIR"))
# statically link these libs # statically link these libs
switch("passL", "bottles/openssl@1.1/lib/libcrypto.a") switch("passL", "bottles/openssl@1.1/lib/libcrypto.a")
switch("passL", "bottles/openssl@1.1/lib/libssl.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/general/service as general_service
import ../../app_service/service/keychain/service as keychain_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/accounts/service as accounts_service
import ../../app_service/service/contacts/service as contacts_service import ../../app_service/service/contacts/service as contacts_service
import ../../app_service/service/language/service as language_service import ../../app_service/service/language/service as language_service
@ -56,6 +57,7 @@ type
# Services # Services
generalService: general_service.Service generalService: general_service.Service
keycardService*: keycard_service.Service
keychainService: keychain_service.Service keychainService: keychain_service.Service
accountsService: accounts_service.Service accountsService: accounts_service.Service
contactsService: contacts_service.Service contactsService: contacts_service.Service
@ -126,7 +128,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.globalUtilsVariant = newQVariant(singletonInstance.utils) result.globalUtilsVariant = newQVariant(singletonInstance.utils)
# Services # Services
result.generalService = general_service.newService() result.generalService = general_service.newService()
result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration, result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService) result.settingsService)
result.keychainService = keychain_service.newService(statusFoundation.events) result.keychainService = keychain_service.newService(statusFoundation.events)
@ -189,7 +192,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.keychainService, result.keychainService,
result.accountsService, result.accountsService,
result.generalService, result.generalService,
result.profileService result.profileService,
result.keycardService
) )
result.mainModule = main_module.newModule[AppController]( result.mainModule = main_module.newModule[AppController](
result, result,
@ -272,6 +276,7 @@ proc delete*(self: AppController) =
self.generalService.delete self.generalService.delete
self.ensService.delete self.ensService.delete
self.gifService.delete self.gifService.delete
self.keycardService.delete
proc startupDidLoad*(self: AppController) = proc startupDidLoad*(self: AppController) =
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant) singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
@ -290,6 +295,7 @@ proc mainDidLoad*(self: AppController) =
self.mainModule.checkForStoringPassword() self.mainModule.checkForStoringPassword()
proc start*(self: AppController) = proc start*(self: AppController) =
self.keycardService.init()
self.keychainService.init() self.keychainService.init()
self.generalService.init() self.generalService.init()
self.accountsService.init() self.accountsService.init()

View File

@ -5,8 +5,6 @@ import ../../constants
# Local Account Settings keys: # Local Account Settings keys:
const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain" const LS_KEY_STORE_TO_KEYCHAIN* = "storeToKeychain"
const DEFAULT_STORE_TO_KEYCHAIN = "notNow" const DEFAULT_STORE_TO_KEYCHAIN = "notNow"
const LS_KEY_IS_KEYCARD_ENABLED* = "isKeycardEnabled"
const DEFAULT_IS_KEYCARD_ENABLED = false
# Local Account Settings values: # Local Account Settings values:
const LS_VALUE_STORE* = "store" const LS_VALUE_STORE* = "store"
const LS_VALUE_NOTNOW* = "notNow" const LS_VALUE_NOTNOW* = "notNow"
@ -35,23 +33,17 @@ QtObject:
proc setFileName*(self: LocalAccountSettings, fileName: string) = proc setFileName*(self: LocalAccountSettings, fileName: string) =
if(not self.settings.isNil): if(not self.settings.isNil):
self.settings.delete self.settings.delete
let filePath = os.joinPath(self.settingsFileDir, fileName) let filePath = os.joinPath(self.settingsFileDir, fileName)
self.settings = newQSettings(filePath, QSettingsFormat.IniFormat) self.settings = newQSettings(filePath, QSettingsFormat.IniFormat)
proc storeToKeychainValueChanged*(self: LocalAccountSettings) {.signal.} proc storeToKeychainValueChanged*(self: LocalAccountSettings) {.signal.}
proc isKeycardEnabledChanged*(self: LocalAccountSettings) {.signal.}
proc removeKey*(self: LocalAccountSettings, key: string) = proc removeKey*(self: LocalAccountSettings, key: string) =
if(self.settings.isNil): if(self.settings.isNil):
return return
self.settings.remove(key) self.settings.remove(key)
if(key == LS_KEY_STORE_TO_KEYCHAIN): if(key == LS_KEY_STORE_TO_KEYCHAIN):
self.storeToKeychainValueChanged() self.storeToKeychainValueChanged()
elif(key == LS_KEY_IS_KEYCARD_ENABLED):
self.isKeycardEnabledChanged()
proc getStoreToKeychainValue*(self: LocalAccountSettings): string {.slot.} = proc getStoreToKeychainValue*(self: LocalAccountSettings): string {.slot.} =
if(self.settings.isNil): if(self.settings.isNil):
@ -70,22 +62,3 @@ QtObject:
read = getStoreToKeychainValue read = getStoreToKeychainValue
write = setStoreToKeychainValue write = setStoreToKeychainValue
notify = storeToKeychainValueChanged 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,8 +91,10 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args): self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e) let args = KeyChainServiceArg(e)
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN) # with the following condition we guard unintentional props deletion from the `.ini` file
self.delegate.emitStoringPasswordError(args.errDescription) if self.accountsService.getLoggedInAccount().isValid():
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN)
self.delegate.emitStoringPasswordError(args.errDescription)
self.events.on(SIGNAL_COMMUNITY_JOINED) do(e:Args): self.events.on(SIGNAL_COMMUNITY_JOINED) do(e:Args):
let args = CommunityArgs(e) let args = CommunityArgs(e)
@ -253,7 +255,7 @@ proc storePassword*(self: Controller, password: string) =
if (value != LS_VALUE_STORE or account.name.len == 0): if (value != LS_VALUE_STORE or account.name.len == 0):
return return
self.keychainService.storePassword(account.name, password) self.keychainService.storeData(account.name, password)
proc getActiveSectionId*(self: Controller): string = proc getActiveSectionId*(self: Controller): string =
result = self.activeSectionId result = self.activeSectionId

View File

@ -1,4 +1,4 @@
import chronicles import chronicles, strutils, os
import io_interface 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/accounts/service as accounts_service
import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/profile/service as profile_service import ../../../app_service/service/profile/service as profile_service
import ../../../app_service/service/keycard/service as keycard_service
logScope: logScope:
topics = "startup-controller" topics = "startup-controller"
type ProfileImageDetails = object type ProfileImageDetails = object
url*: string url*: string
croppedImage*: string
x1*: int x1*: int
y1*: int y1*: int
x2*: int x2*: int
@ -28,18 +30,29 @@ type
accountsService: accounts_service.Service accountsService: accounts_service.Service
keychainService: keychain_service.Service keychainService: keychain_service.Service
profileService: profile_service.Service profileService: profile_service.Service
keycardService: keycard_service.Service
tmpProfileImageDetails: ProfileImageDetails tmpProfileImageDetails: ProfileImageDetails
tmpDisplayName: string tmpDisplayName: string
tmpPassword: string tmpPassword: string
tmpMnemonic: string
tmpSelectedLoginAccountKeyUid: 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, proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
generalService: general_service.Service, generalService: general_service.Service,
accountsService: accounts_service.Service, accountsService: accounts_service.Service,
keychainService: keychain_service.Service, keychainService: keychain_service.Service,
profileService: profile_service.Service): profileService: profile_service.Service,
keycardService: keycard_service.Service):
Controller = Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
@ -48,6 +61,14 @@ proc newController*(delegate: io_interface.AccessInterface,
result.accountsService = accountsService result.accountsService = accountsService
result.keychainService = keychainService result.keychainService = keychainService
result.profileService = profileService 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) = proc delete*(self: Controller) =
discard discard
@ -56,10 +77,12 @@ proc init*(self: Controller) =
self.events.on(SignalType.NodeLogin.event) do(e:Args): self.events.on(SignalType.NodeLogin.event) do(e:Args):
let signal = NodeSignal(e) let signal = NodeSignal(e)
self.delegate.onNodeLogin(signal.event.error) self.delegate.onNodeLogin(signal.event.error)
self.cleanTmpData()
self.events.on(SignalType.NodeStopped.event) do(e:Args): self.events.on(SignalType.NodeStopped.event) do(e:Args):
self.events.emit("nodeStopped", Args()) self.events.emit("nodeStopped", Args())
self.accountsService.clear() self.accountsService.clear()
self.cleanTmpData()
self.delegate.emitLogOut() self.delegate.emitLogOut()
self.events.on(SignalType.NodeReady.event) do(e:Args): 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): self.events.on(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e) let args = KeyChainServiceArg(e)
# We are notifying user only about keychain errors. self.tmpKeychainErrorOccurred = true
if (args.errType == ERROR_TYPE_AUTHENTICATION): self.delegate.emitObtainingPasswordError(args.errDescription, args.errType)
return
singletonInstance.localAccountSettings.removeKey(LS_KEY_STORE_TO_KEYCHAIN) self.events.on(SignalKeycardResponse) do(e: Args):
self.delegate.emitObtainingPasswordError(args.errDescription) let args = KeycardArgs(e)
self.delegate.onKeycardResponse(args.flowType, args.flowEvent)
proc shouldStartWithOnboardingScreen*(self: Controller): bool = proc shouldStartWithOnboardingScreen*(self: Controller): bool =
return self.accountsService.openedAccounts().len == 0 return self.accountsService.openedAccounts().len == 0
@ -86,22 +110,22 @@ proc getGeneratedAccounts*(self: Controller): seq[GeneratedAccountDto] =
proc getImportedAccount*(self: Controller): GeneratedAccountDto = proc getImportedAccount*(self: Controller): GeneratedAccountDto =
return self.accountsService.getImportedAccount() 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 = proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName) return self.generalService.getPasswordStrengthScore(password, userName)
proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string = proc generateImage*(self: Controller, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string =
let formatedImg = singletonInstance.utils.formatImagePath(imageUrl) 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): if(images.len == 0):
return return
for img in images: for img in images:
if(img.imgType == "large"): 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 return img.uri
proc getCroppedProfileImage*(self: Controller): string =
return self.tmpProfileImageDetails.croppedImage
proc setDisplayName*(self: Controller, value: string) = proc setDisplayName*(self: Controller, value: string) =
self.tmpDisplayName = value self.tmpDisplayName = value
@ -114,12 +138,83 @@ proc setPassword*(self: Controller, value: string) =
proc getPassword*(self: Controller): string = proc getPassword*(self: Controller): string =
return self.tmpPassword 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) = proc storePasswordToKeychain(self: Controller) =
let account = self.accountsService.getLoggedInAccount() let account = self.accountsService.getLoggedInAccount()
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue() let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE or account.name.len == 0): if (value != LS_VALUE_STORE or account.name.len == 0):
return 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) = proc storeIdentityImage*(self: Controller) =
if self.tmpProfileImageDetails.url.len == 0: 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.y1, self.tmpProfileImageDetails.x2, self.tmpProfileImageDetails.y2)
self.tmpProfileImageDetails = ProfileImageDetails() self.tmpProfileImageDetails = ProfileImageDetails()
proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool) = proc validMnemonic*(self: Controller, mnemonic: string): bool =
let error = self.accountsService.setupAccount(accountId, self.tmpPassword, self.tmpDisplayName) 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 != "": if error != "":
self.delegate.setupAccountError(error) self.delegate.setupAccountError(error)
else: else:
@ -140,8 +251,6 @@ proc setupAccount(self: Controller, accountId: string, storeToKeychain: bool) =
self.storePasswordToKeychain() self.storePasswordToKeychain()
else: else:
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER) singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
self.setPassword("")
self.setDisplayName("")
proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) = proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accounts = self.getGeneratedAccounts() let accounts = self.getGeneratedAccounts()
@ -149,27 +258,30 @@ proc storeGeneratedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
error "list of generated accounts is empty" error "list of generated accounts is empty"
return return
let accountId = accounts[0].id let accountId = accounts[0].id
self.setupAccount(accountId, storeToKeychain) self.setupAccount(accountId, storeToKeychain, keycardUsage = false)
proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool) = proc storeImportedAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let accountId = self.getImportedAccount().id let accountId = self.getImportedAccount().id
self.setupAccount(accountId, storeToKeychain) self.setupAccount(accountId, storeToKeychain, keycardUsage = false)
proc validMnemonic*(self: Controller, mnemonic: string): bool = proc storeKeycardAccountAndLogin*(self: Controller, storeToKeychain: bool) =
let err = self.accountsService.validateMnemonic(mnemonic) if self.importMnemonic():
if err.len == 0: let accountId = self.getImportedAccount().id
self.tmpMnemonic = mnemonic self.setupAccount(accountId, storeToKeychain, keycardUsage = true)
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
else: else:
self.delegate.importAccountError(error) error "an error ocurred while importing mnemonic"
return false
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] = proc getOpenedAccounts*(self: Controller): seq[AccountDto] =
return self.accountsService.openedAccounts() return self.accountsService.openedAccounts()
@ -180,21 +292,95 @@ proc getSelectedLoginAccount(self: Controller): AccountDto =
if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid): if(acc.keyUid == self.tmpSelectedLoginAccountKeyUid):
return acc return acc
proc keyUidMatch*(self: Controller, keyUid: string): bool =
return self.tmpSelectedLoginAccountKeyUid == keyUid
proc setSelectedLoginAccountKeyUid*(self: Controller, keyUid: string) = proc setSelectedLoginAccountKeyUid*(self: Controller, keyUid: string) =
self.tmpSelectedLoginAccountKeyUid = keyUid 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 # Dealing with Keychain is the MacOS only feature
if(not defined(macosx)): if(not defined(macosx)):
return return
let selectedAccount = self.getSelectedLoginAccount()
singletonInstance.localAccountSettings.setFileName(selectedAccount.name)
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue() let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if (value != LS_VALUE_STORE): if (value != LS_VALUE_STORE):
return return
self.keychainService.tryToObtainPassword(selectedAccount.name) self.tmpKeychainErrorOccurred = false
let selectedAccount = self.getSelectedLoginAccount()
self.keychainService.tryToObtainData(selectedAccount.name)
proc login*(self: Controller) = proc login*(self: Controller) =
let selectedAccount = self.getSelectedLoginAccount() let selectedAccount = self.getSelectedLoginAccount()
let error = self.accountsService.login(selectedAccount, self.tmpPassword) let error = self.accountsService.login(selectedAccount, self.tmpPassword)
self.setPassword("")
if(error.len > 0): if(error.len > 0):
self.delegate.emitAccountLoginError(error) 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 type
BiometricsState* = ref object of State BiometricsState* = ref object of State
@ -11,28 +8,36 @@ proc newBiometricsState*(flowType: FlowType, backState: State): BiometricsState
proc delete*(self: BiometricsState) = proc delete*(self: BiometricsState) =
self.State.delete self.State.delete
method moveToNextPrimaryState*(self: BiometricsState): bool =
return false
method moveToNextSecondaryState*(self: BiometricsState): bool =
return false
method executePrimaryCommand*(self: BiometricsState, controller: Controller) = method executePrimaryCommand*(self: BiometricsState, controller: Controller) =
let storeToKeychain = true # true, cause we have support for keychain for mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys: 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: 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: 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 ## This should not be the correct call for this flow, this is an issue,
## and this is not a bug fixing issue, left as it is. ## 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 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) = 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: 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: 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: 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 ## This should not be the correct call for this flow, this is an issue,
## and this is not a bug fixing issue, left as it is. ## 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 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 type
LoginState* = ref object of State LoginState* = ref object of State
@ -12,14 +8,22 @@ proc newLoginState*(flowType: FlowType, backState: State): LoginState =
proc delete*(self: LoginState) = proc delete*(self: LoginState) =
self.State.delete self.State.delete
method moveToNextPrimaryState*(self: LoginState): bool =
return false
method executePrimaryCommand*(self: LoginState, controller: Controller) = method executePrimaryCommand*(self: LoginState, controller: Controller) =
controller.login() controller.login()
method getNextSecondaryState*(self: LoginState): State = method getNextSecondaryState*(self: LoginState, controller: Controller): State =
return newWelcomeStateNewUser(FlowType.General, self) return createState(StateType.WelcomeNewStatusUser, self.flowType, self)
method getNextTertiaryState*(self: LoginState): State = method getNextTertiaryState*(self: LoginState, controller: Controller): State =
return newWelcomeStateOldUser(FlowType.General, self) 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 type
NotificationState* = ref object of State NotificationState* = ref object of State
@ -10,5 +8,5 @@ proc newNotificationState*(flowType: FlowType, backState: State): NotificationSt
proc delete*(self: NotificationState) = proc delete*(self: NotificationState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: NotificationState): State = method getNextPrimaryState*(self: NotificationState, controller: Controller): State =
return newWelcomeState(FlowType.General, nil) return createState(StateType.Welcome, FlowType.General, nil)

View File

@ -1,10 +1,14 @@
import ../controller import ../controller
from ../../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
export KeycardEvent, KeyDetails
type FlowType* {.pure.} = enum type FlowType* {.pure.} = enum
General = "General" General = "General"
FirstRunNewUserNewKeys = "FirstRunNewUserNewKeys" FirstRunNewUserNewKeys = "FirstRunNewUserNewKeys"
FirstRunNewUserNewKeycardKeys = "FirstRunNewUserNewKeycardKeys" FirstRunNewUserNewKeycardKeys = "FirstRunNewUserNewKeycardKeys"
FirstRunNewUserImportSeedPhrase = "FirstRunNewUserImportSeedPhrase" FirstRunNewUserImportSeedPhrase = "FirstRunNewUserImportSeedPhrase"
FirstRunNewUserImportSeedPhraseIntoKeycard = "FirstRunNewUserImportSeedPhraseIntoKeycard"
FirstRunOldUserSyncCode = "FirstRunOldUserSyncCode" FirstRunOldUserSyncCode = "FirstRunOldUserSyncCode"
FirstRunOldUserKeycardImport = "FirstRunOldUserKeycardImport" FirstRunOldUserKeycardImport = "FirstRunOldUserKeycardImport"
FirstRunOldUserImportSeedPhrase = "FirstRunOldUserImportSeedPhrase" FirstRunOldUserImportSeedPhrase = "FirstRunOldUserImportSeedPhrase"
@ -23,7 +27,35 @@ type StateType* {.pure.} = enum
UserProfileImportSeedPhrase = "UserProfileImportSeedPhrase" UserProfileImportSeedPhrase = "UserProfileImportSeedPhrase"
UserProfileEnterSeedPhrase = "UserProfileEnterSeedPhrase" UserProfileEnterSeedPhrase = "UserProfileEnterSeedPhrase"
Biometrics = "Biometrics" 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" 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. ## 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). ## 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 return not self.backState.isNil
## Returns next state instance in case the "primary" action is triggered ## 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 return nil
## Returns next state instance in case the "secondary" action is triggered ## 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 return nil
## Returns next state instance in case the "tertiary" action is triggered ## 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 return nil
## This method is executed in case "back" button is clicked ## 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.} = method executeTertiaryCommand*(self: State, controller: Controller) {.inline base.} =
discard discard
## Returns true if we should move from this state immediatelly when the "primary" action is triggered, ## This method is used for handling aync responses for keycard related states
## in case we need to wait for some other action, or some aync event, this should return false method resolveKeycardNextState*(self: State, keycardFlowType: string, keycardEvent: KeycardEvent,
method moveToNextPrimaryState*(self: State): bool {.inline base.} = controller: Controller): State {.inline base.} =
return true return nil
## 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

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 type
UserProfileChatKeyState* = ref object of State UserProfileChatKeyState* = ref object of State
@ -11,5 +8,12 @@ proc newUserProfileChatKeyState*(flowType: FlowType, backState: State): UserProf
proc delete*(self: UserProfileChatKeyState) = proc delete*(self: UserProfileChatKeyState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: UserProfileChatKeyState): State = method getNextPrimaryState*(self: UserProfileChatKeyState, controller: Controller): State =
return newUserProfileCreatePasswordState(self.State.flowType, self) 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 type
UserProfileConfirmPasswordState* = ref object of State UserProfileConfirmPasswordState* = ref object of State
@ -12,23 +8,23 @@ proc newUserProfileConfirmPasswordState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileConfirmPasswordState) = proc delete*(self: UserProfileConfirmPasswordState) =
self.State.delete self.State.delete
method moveToNextPrimaryState*(self: UserProfileConfirmPasswordState): bool = method getNextPrimaryState*(self: UserProfileConfirmPasswordState, controller: Controller): State =
return defined(macosx) if not defined(macosx):
method getNextPrimaryState*(self: UserProfileConfirmPasswordState): State =
if not self.moveToNextPrimaryState():
return nil return nil
return newBiometricsState(self.State.flowType, nil) return createState(StateType.Biometrics, self.flowType, self)
method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) = method executePrimaryCommand*(self: UserProfileConfirmPasswordState, controller: Controller) =
if self.moveToNextPrimaryState(): if defined(macosx):
return return
let storeToKeychain = false # false, cause we don't have keychain support for other than mac os
if self.flowType == FlowType.FirstRunNewUserNewKeys: 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: 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: 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 ## This should not be the correct call for this flow, this is an issue,
## and this is not a bug fixing issue, left as it is. ## 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 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 type
UserProfileCreatePasswordState* = ref object of State UserProfileCreatePasswordState* = ref object of State
@ -12,8 +8,8 @@ proc newUserProfileCreatePasswordState*(flowType: FlowType, backState: State): U
proc delete*(self: UserProfileCreatePasswordState) = proc delete*(self: UserProfileCreatePasswordState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: UserProfileCreatePasswordState): State = method getNextPrimaryState*(self: UserProfileCreatePasswordState, controller: Controller): State =
return newUserProfileConfirmPasswordState(self.State.flowType, self) return createState(StateType.UserProfileConfirmPassword, self.flowType, self)
method executeBackCommand*(self: UserProfileCreatePasswordState, controller: Controller) = method executeBackCommand*(self: UserProfileCreatePasswordState, controller: Controller) =
controller.setPassword("") controller.setPassword("")

View File

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

View File

@ -1,7 +1,3 @@
import state
import ../controller
import user_profile_create_state
type type
UserProfileEnterSeedPhraseState* = ref object of State UserProfileEnterSeedPhraseState* = ref object of State
successfulImport: bool successfulImport: bool
@ -14,13 +10,44 @@ proc newUserProfileEnterSeedPhraseState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileEnterSeedPhraseState) = proc delete*(self: UserProfileEnterSeedPhraseState) =
self.State.delete self.State.delete
method moveToNextPrimaryState*(self: UserProfileEnterSeedPhraseState): bool = method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState, controller: Controller): State =
return self.successfulImport if not self.successfulImport:
method getNextPrimaryState*(self: UserProfileEnterSeedPhraseState): State =
if not self.moveToNextPrimaryState():
return nil 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) = method executePrimaryCommand*(self: UserProfileEnterSeedPhraseState, controller: Controller) =
self.successfulImport = controller.importMnemonic() 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 type
UserProfileImportSeedPhraseState* = ref object of State UserProfileImportSeedPhraseState* = ref object of State
@ -11,5 +8,8 @@ proc newUserProfileImportSeedPhraseState*(flowType: FlowType, backState: State):
proc delete*(self: UserProfileImportSeedPhraseState) = proc delete*(self: UserProfileImportSeedPhraseState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: UserProfileImportSeedPhraseState): State = method getNextPrimaryState*(self: UserProfileImportSeedPhraseState, controller: Controller): State =
return newUserProfileEnterSeedPhraseState(self.State.flowType, self) 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 type
WelcomeState* = ref object of State WelcomeState* = ref object of State
@ -11,8 +8,8 @@ proc newWelcomeState*(flowType: FlowType, backState: State): WelcomeState =
proc delete*(self: WelcomeState) = proc delete*(self: WelcomeState) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: WelcomeState): State = method getNextPrimaryState*(self: WelcomeState, controller: Controller): State =
return newWelcomeStateNewUser(FlowType.General, self) return createState(StateType.WelcomeNewStatusUser, FlowType.General, self)
method getNextSecondaryState*(self: WelcomeState): State = method getNextSecondaryState*(self: WelcomeState, controller: Controller): State =
return newWelcomeStateOldUser(FlowType.General, self) 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 type
WelcomeStateNewUser* = ref object of State WelcomeStateNewUser* = ref object of State
@ -11,14 +8,15 @@ proc newWelcomeStateNewUser*(flowType: FlowType, backState: State): WelcomeState
proc delete*(self: WelcomeStateNewUser) = proc delete*(self: WelcomeStateNewUser) =
self.State.delete self.State.delete
method getNextPrimaryState*(self: WelcomeStateNewUser): State = method executeBackCommand*(self: WelcomeStateNewUser, controller: Controller) =
return newUserProfileCreateState(FlowType.FirstRunNewUserNewKeys, self) if self.flowType == FlowType.AppLogin and controller.isKeycardCreatedAccountSelectedOne():
controller.runLoginFlow()
method getNextSecondaryState*(self: WelcomeStateNewUser): State = method getNextPrimaryState*(self: WelcomeStateNewUser, controller: Controller): State =
# We will handle here a click on `Generate keys for a new Keycard` return createState(StateType.UserProfileCreate, FlowType.FirstRunNewUserNewKeys, self)
discard
method getNextTertiaryState*(self: WelcomeStateNewUser): State =
return newUserProfileImportSeedPhraseState(FlowType.FirstRunNewUserImportSeedPhrase, 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 type
WelcomeStateOldUser* = ref object of State WelcomeStateOldUser* = ref object of State
@ -11,19 +8,17 @@ proc newWelcomeStateOldUser*(flowType: FlowType, backState: State): WelcomeState
proc delete*(self: WelcomeStateOldUser) = proc delete*(self: WelcomeStateOldUser) =
self.State.delete 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` # We will handle here a click on `Scan sync code`
discard discard
method getNextSecondaryState*(self: WelcomeStateOldUser): State = method getNextSecondaryState*(self: WelcomeStateOldUser, controller: Controller): State =
# We will handle here a click on `Login with Keycard` return createState(StateType.KeycardPluginReader, FlowType.FirstRunOldUserKeycardImport, self)
discard
method getNextTertiaryState*(self: WelcomeStateOldUser): State = method getNextTertiaryState*(self: WelcomeStateOldUser, controller: Controller): State =
## This is added as next state in case of import seed for an old user, but this doesn't match the flow return createState(StateType.UserProfileEnterSeedPhrase, FlowType.FirstRunOldUserImportSeedPhrase, self)
## 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)

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 import models/login_account_item as login_acc_item
from ../../../app_service/service/keycard/service import KeycardEvent, KeyDetails
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj 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.} = method generateImage*(self: AccessInterface, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.base.} =
raise newException(ValueError, "No implementation available") 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.} = method setDisplayName*(self: AccessInterface, value: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -49,6 +54,15 @@ method setPassword*(self: AccessInterface, value: string) {.base.} =
method getPassword*(self: AccessInterface): string {.base.} = method getPassword*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available") 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.} = method getPasswordStrengthScore*(self: AccessInterface, password: string, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -73,12 +87,24 @@ method onNodeLogin*(self: AccessInterface, error: string) {.base.} =
method emitAccountLoginError*(self: AccessInterface, error: string) {.base.} = method emitAccountLoginError*(self: AccessInterface, error: string) {.base.} =
raise newException(ValueError, "No implementation available") 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") raise newException(ValueError, "No implementation available")
method emitObtainingPasswordSuccess*(self: AccessInterface, password: string) {.base.} = method emitObtainingPasswordSuccess*(self: AccessInterface, password: string) {.base.} =
raise newException(ValueError, "No implementation available") 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 # This way (using concepts) is used only for the modules managed by AppController
type type
DelegateInterface* = concept c DelegateInterface* = concept c

View File

@ -12,8 +12,10 @@ type
colorHash: color_hash_model.Model colorHash: color_hash_model.Model
colorHashVariant: QVariant colorHashVariant: QVariant
colorId: int 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 = Item =
result.name = name result.name = name
result.thumbnailImage = thumbnailImage 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.colorHash.setItems(map(colorHash, x => color_hash_item.initItem(x.len, x.colorIdx)))
result.colorHashVariant = newQVariant(result.colorHash) result.colorHashVariant = newQVariant(result.colorHash)
result.colorId = colorId result.colorId = colorId
result.keycardPairing = keycardPairing
proc getName*(self: Item): string = proc getName*(self: Item): string =
return self.name return self.name
@ -44,3 +47,9 @@ proc getColorHashVariant*(self: Item): QVariant =
proc getColorId*(self: Item): int = proc getColorId*(self: Item): int =
return self.colorId 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 KeyUid
ColorHash ColorHash
ColorId ColorId
KeycardPairing
KeycardCreatedAccount
QtObject: QtObject:
type type
@ -37,7 +39,9 @@ QtObject:
ModelRole.LargeImage.int:"largeImage", ModelRole.LargeImage.int:"largeImage",
ModelRole.KeyUid.int:"keyUid", ModelRole.KeyUid.int:"keyUid",
ModelRole.ColorHash.int:"colorHash", ModelRole.ColorHash.int:"colorHash",
ModelRole.ColorId.int:"colorId" ModelRole.ColorId.int:"colorId",
ModelRole.KeycardPairing.int:"keycardPairing",
ModelRole.KeycardCreatedAccount.int:"keycardCreatedAccount"
}.toTable }.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant = method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -63,6 +67,10 @@ QtObject:
result = newQVariant(item.getColorHash()) result = newQVariant(item.getColorHash())
of ModelRole.ColorId: of ModelRole.ColorId:
result = newQVariant(item.getColorId()) 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]) = proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel() self.beginResetModel()

View File

@ -2,7 +2,7 @@ import NimQml, chronicles
import io_interface import io_interface
import view, controller 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/generated_account_item as gen_acc_item
import models/login_account_item as login_acc_item import models/login_account_item as login_acc_item
import ../../global/global_singleton 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/accounts/service as accounts_service
import ../../../app_service/service/general/service as general_service import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/profile/service as profile_service import ../../../app_service/service/profile/service as profile_service
import ../../../app_service/service/keycard/service as keycard_service
export io_interface export io_interface
@ -30,14 +31,15 @@ proc newModule*[T](delegate: T,
keychainService: keychain_service.Service, keychainService: keychain_service.Service,
accountsService: accounts_service.Service, accountsService: accounts_service.Service,
generalService: general_service.Service, generalService: general_service.Service,
profileService: profile_service.Service): profileService: profile_service.Service,
keycardService: keycard_service.Service):
Module[T] = Module[T] =
result = Module[T]() result = Module[T]()
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, generalService, accountsService, keychainService, result.controller = controller.newController(result, events, generalService, accountsService, keychainService,
profileService) profileService, keycardService)
method delete*[T](self: Module[T]) = method delete*[T](self: Module[T]) =
self.view.delete self.view.delete
@ -69,19 +71,21 @@ method load*[T](self: Module[T]) =
self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil)) self.view.setCurrentStartupState(newWelcomeState(FlowType.General, nil))
else: else:
let openedAccounts = self.controller.getOpenedAccounts() let openedAccounts = self.controller.getOpenedAccounts()
if(openedAccounts.len > 0): var items: seq[login_acc_item.Item]
var items: seq[login_acc_item.Item] for acc in openedAccounts:
for acc in openedAccounts: var thumbnailImage: string
var thumbnailImage: string var largeImage: string
var largeImage: string self.extractImages(acc, thumbnailImage, largeImage)
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) self.view.setLoginAccountsModelItems(items)
# set the first account as slected one # set the first account as slected one
if items.len == 0: if items.len == 0:
error "cannot run the app in login flow cause list of login accounts is empty" # we should never be here, since else block of `if (shouldStartWithOnboardingScreen)`
quit() # quit the app # ensures that `openedAccounts` is not empty array
self.setSelectedLoginAccount(items[0]) error "cannot run the app in login flow cause list of login accounts is empty"
quit() # quit the app
self.setSelectedLoginAccount(items[0])
self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil)) self.view.setCurrentStartupState(newLoginState(FlowType.AppLogin, nil))
self.delegate.startupDidLoad() self.delegate.startupDidLoad()
@ -96,35 +100,54 @@ method emitLogOut*[T](self: Module[T]) =
method onBackActionClicked*[T](self: Module[T]) = method onBackActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj() let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil: if currStateObj.isNil:
currStateObj.executeBackCommand(self.controller) error "cannot resolve current state"
let backState = currStateObj.getBackState() return
self.view.setCurrentStartupState(backState) debug "back_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
currStateObj.delete() 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]) = method onPrimaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj() let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil: if currStateObj.isNil:
currStateObj.executePrimaryCommand(self.controller) error "cannot resolve current state"
if currStateObj.moveToNextPrimaryState(): return
let nextState = currStateObj.getNextPrimaryState() debug "primary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
self.view.setCurrentStartupState(nextState) currStateObj.executePrimaryCommand(self.controller)
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]) = method onSecondaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj() let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil: if currStateObj.isNil:
currStateObj.executeSecondaryCommand(self.controller) error "cannot resolve current state"
if currStateObj.moveToNextSecondaryState(): return
let nextState = currStateObj.getNextSecondaryState() debug "secondary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
self.view.setCurrentStartupState(nextState) currStateObj.executeSecondaryCommand(self.controller)
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]) = method onTertiaryActionClicked*[T](self: Module[T]) =
let currStateObj = self.view.currentStartupStateObj() let currStateObj = self.view.currentStartupStateObj()
if not currStateObj.isNil: if currStateObj.isNil:
currStateObj.executeTertiaryCommand(self.controller) error "cannot resolve current state"
if currStateObj.moveToNextTertiaryState(): return
let nextState = currStateObj.getNextTertiaryState() debug "tertiary_action", currFlow=currStateObj.flowType(), currState=currStateObj.stateType()
self.view.setCurrentStartupState(nextState) currStateObj.executeTertiaryCommand(self.controller)
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 = method getImportedAccount*[T](self: Module[T]): GeneratedAccountDto =
return self.controller.getImportedAccount() 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 = 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) 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) = method setDisplayName*[T](self: Module[T], value: string) =
self.controller.setDisplayName(value) self.controller.setDisplayName(value)
@ -144,6 +170,15 @@ method setPassword*[T](self: Module[T], value: string) =
method getPassword*[T](self: Module[T]): string = method getPassword*[T](self: Module[T]): string =
return self.controller.getPassword() 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 = method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int =
return self.controller.getPasswordStrengthScore(password, userName) 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) = method setSelectedLoginAccount*[T](self: Module[T], item: login_acc_item.Item) =
self.controller.setSelectedLoginAccountKeyUid(item.getKeyUid()) 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) self.view.setSelectedLoginAccount(item)
method emitAccountLoginError*[T](self: Module[T], error: string) = method emitAccountLoginError*[T](self: Module[T], error: string) =
self.view.emitAccountLoginError(error) self.view.emitAccountLoginError(error)
method emitObtainingPasswordError*[T](self: Module[T], errorDescription: string) = method emitObtainingPasswordError*[T](self: Module[T], errorDescription: string, errorType: string) =
self.view.emitObtainingPasswordError(errorDescription) self.view.emitObtainingPasswordError(errorDescription, errorType)
method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) = method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) =
self.view.emitObtainingPasswordSuccess(password) self.view.emitObtainingPasswordSuccess(password)
@ -175,7 +215,7 @@ method emitObtainingPasswordSuccess*[T](self: Module[T], password: string) =
method onNodeLogin*[T](self: Module[T], error: string) = method onNodeLogin*[T](self: Module[T], error: string) =
let currStateObj = self.view.currentStartupStateObj() let currStateObj = self.view.currentStartupStateObj()
if currStateObj.isNil: if currStateObj.isNil:
error "error: cannot determine current startup state" error "cannot determine current startup state"
quit() # quit the app quit() # quit the app
if error.len == 0: if error.len == 0:
@ -187,4 +227,37 @@ method onNodeLogin*[T](self: Module[T], error: string) =
self.emitAccountLoginError(error) self.emitAccountLoginError(error)
else: else:
self.setupAccountError(error) 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.} = proc getKeyUid(self: SelectedLoginAccount): string {.slot.} =
return self.item.getKeyUid() return self.item.getKeyUid()
QtProperty[string] keyUid: QtProperty[string] keyUid:
read = getKeyUid 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.} = proc getColorHash(self: SelectedLoginAccount): QVariant {.slot.} =
return self.item.getColorHashVariant() return self.item.getColorHashVariant()
QtProperty[QVariant] colorHash: QtProperty[QVariant] colorHash:
read = getColorHash read = getColorHash
proc getColorId(self: SelectedLoginAccount): int {.slot.} = proc getColorId(self: SelectedLoginAccount): int {.slot.} =
return self.item.getColorId() return self.item.getColorId()
QtProperty[int] colorId: QtProperty[int] colorId:
read = getColorId read = getColorId
proc getThumbnailImage(self: SelectedLoginAccount): string {.slot.} = proc getThumbnailImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getThumbnailImage() return self.item.getThumbnailImage()
QtProperty[string] thumbnailImage: QtProperty[string] thumbnailImage:
read = getThumbnailImage read = getThumbnailImage
proc getLargeImage(self: SelectedLoginAccount): string {.slot.} = proc getLargeImage(self: SelectedLoginAccount): string {.slot.} =
return self.item.getLargeImage() return self.item.getLargeImage()
QtProperty[string] largeImage: QtProperty[string] largeImage:
read = getLargeImage read = getLargeImage

View File

@ -26,6 +26,7 @@ QtObject:
loginAccountsModel: login_acc_model.Model loginAccountsModel: login_acc_model.Model
loginAccountsModelVariant: QVariant loginAccountsModelVariant: QVariant
appState: AppState 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) = proc delete*(self: View) =
self.currentStartupStateVariant.delete self.currentStartupStateVariant.delete
@ -133,7 +134,10 @@ QtObject:
notify = importedAccountChanged notify = importedAccountChanged
proc generateImage*(self: View, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} = 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.} = proc setDisplayName*(self: View, value: string) {.slot.} =
self.delegate.setDisplayName(value) self.delegate.setDisplayName(value)
@ -147,6 +151,15 @@ QtObject:
proc getPassword*(self: View): string {.slot.} = proc getPassword*(self: View): string {.slot.} =
return self.delegate.getPassword() 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.} = proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName) return self.delegate.getPasswordStrengthScore(password, userName)
@ -194,10 +207,28 @@ QtObject:
proc emitAccountLoginError*(self: View, error: string) = proc emitAccountLoginError*(self: View, error: string) =
self.accountLoginError(error) self.accountLoginError(error)
proc obtainingPasswordError*(self:View, errorDescription: string) {.signal.} proc obtainingPasswordError*(self:View, errorDescription: string, errorType: string) {.signal.}
proc emitObtainingPasswordError*(self: View, errorDescription: string) = proc emitObtainingPasswordError*(self: View, errorDescription: string, errorType: string) =
self.obtainingPasswordError(errorDescription) self.obtainingPasswordError(errorDescription, errorType)
proc obtainingPasswordSuccess*(self:View, password: string) {.signal.} proc obtainingPasswordSuccess*(self:View, password: string) {.signal.}
proc emitObtainingPasswordSuccess*(self: View, password: string) = proc emitObtainingPasswordSuccess*(self: View, password: string) =
self.obtainingPasswordSuccess(password) 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 json_serialization, chronicles
import ../../../app/global/global_singleton import ../../../app/global/global_singleton
import ./dto/accounts as dto_accounts import ./dto/accounts as dto_accounts
import ./dto/generated_accounts as dto_generated_accounts import ./dto/generated_accounts as dto_generated_accounts
from ../keycard/service import KeycardEvent, KeyDetails
import ../../../backend/general as status_general import ../../../backend/general as status_general
import ../../../backend/core as status_core import ../../../backend/core as status_core
@ -143,6 +144,25 @@ proc saveAccountAndLogin(self: Service, hashedPassword: string, account,
except Exception as e: except Exception as e:
error "error: ", procName="saveAccountAndLogin", errName = e.name, errDesription = e.msg 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 = proc prepareAccountJsonObject(self: Service, account: GeneratedAccountDto, displayName: string): JsonNode =
result = %* { result = %* {
"name": if displayName == "": account.alias else: displayName, "name": if displayName == "": account.alias else: displayName,
@ -259,15 +279,32 @@ proc setLocalAccountSettingsFile(self: Service) =
if(defined(macosx) and self.getLoggedInAccount.isValid()): if(defined(macosx) and self.getLoggedInAccount.isValid()):
singletonInstance.localAccountSettings.setFileName(self.getLoggedInAccount.name) 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: try:
let installationId = $genUUID() 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) self.setKeyStoreDir(accountDataJson{"key-uid"}.getStr)
let subaccountDataJson = self.getSubaccountDataForAccountId(accountId, displayName) 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) let nodeConfigJson = self.getDefaultNodeConfig(installationId)
if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or 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 error "error: ", procName="setupAccount", errDesription = description
return description return description
let hashedPassword = hashString(password) let hashedPassword = hashString(usedPassword)
discard self.storeAccount(accountId, hashedPassword) discard self.storeAccount(accountId, hashedPassword)
discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS) discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS)
if keycardUsage:
self.addKeycardDetails(settingsJson, accountDataJson)
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson, self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson,
subaccountDataJson, settingsJson, nodeConfigJson) subaccountDataJson, settingsJson, nodeConfigJson)
self.setLocalAccountSettingsFile() 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 error "error: ", procName="setupAccount", errName = e.name, errDesription = e.msg
return 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 = proc importMnemonic*(self: Service, mnemonic: string): string =
if mnemonic.len == 0:
return "empty mnemonic"
try: try:
let response = status_account.multiAccountImportMnemonic(mnemonic) let response = status_account.multiAccountImportMnemonic(mnemonic)
self.importedAccount = toGeneratedAccountDto(response.result) 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 error "error: ", procName="login", errName = e.name, errDesription = e.msg
return 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 = proc verifyAccountPassword*(self: Service, account: string, password: string): bool =
try: try:
let response = status_account.verifyAccountPassword(account, password, self.keyStoreDir) 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: for k, _ in obj:
result.add k 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) = proc handleRPCErrors*(response: string) =
let parsedReponse = parseJson(response) let parsedReponse = parseJson(response)
if (parsedReponse.hasKey("error")): 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, signalConnect(self.keychainManager, "error(QString, int, QString)", self,
"onKeychainManagerError(QString, int, QString)", 2) "onKeychainManagerError(QString, int, QString)", 2)
proc storePassword*(self: Service, username: string, password: string) = proc storeData*(self: Service, key: string, data: string) =
self.keychainManager.storeDataAsync(username, password) self.keychainManager.storeDataAsync(key, data)
proc tryToObtainPassword*(self: Service, username: string) = proc tryToObtainData*(self: Service, key: string) =
self.keychainManager.readDataAsync(username) self.keychainManager.readDataAsync(key)
proc onKeychainManagerError*(self: Service, errorType: string, errorCode: int, proc onKeychainManagerError*(self: Service, errorType: string, errorCode: int,
errorDescription: string) {.slot.} = errorDescription: string) {.slot.} =
## This slot is called in case an error occured while we're dealing with ## This slot is called in case an error occured while we're dealing with
## KeychainManager. So far we're just logging the error. ## 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, let arg = KeyChainServiceArg(errCode: errorCode, errType: errorType,
errDescription: errorDescription) errDescription: errorDescription)
self.events.emit("", arg) self.events.emit(SIGNAL_KEYCHAIN_SERVICE_ERROR, arg)
proc onKeychainManagerSuccess*(self: Service, data: string) {.slot.} = proc onKeychainManagerSuccess*(self: Service, data: string) {.slot.} =
## This slot is called in case a password is successfully retrieved from the ## 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 error "error doing rpc request", methodName = "saveAccountAndLogin", exception=e.msg
raise newException(RpcException, 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): proc login*(name, keyUid, hashedPassword, thumbnail, large: string, nodeCfgObj: string):
RpcResponse[JsonNode] RpcResponse[JsonNode]
{.raises: [Exception].} = {.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 error "error doing rpc request", methodName = "login", exception=e.msg
raise newException(RpcException, 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): proc verifyAccountPassword*(address: string, password: string, keystoreDir: string):
RpcResponse[JsonNode] {.raises: [Exception].} = RpcResponse[JsonNode] {.raises: [Exception].} =
try: try:

View File

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

View File

@ -1,6 +1,7 @@
import NimQml, chronicles, os, strformat, strutils, times, md5, json import NimQml, chronicles, os, strformat, strutils, times, md5, json
import status_go import status_go
import keycard_go
import app/core/main import app/core/main
import constants import constants
@ -11,6 +12,7 @@ logScope:
topics = "status-app" topics = "status-app"
var signalsManagerQObjPointer: pointer var signalsManagerQObjPointer: pointer
var keycardServiceQObjPointer: pointer
proc isExperimental(): string = proc isExperimental(): string =
result = if getEnv("EXPERIMENTAL") == "1": "1" else: "0" # value explicity passed to avoid trusting input 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 # 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 # 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) # 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: if signalsManagerQObjPointer != nil:
signal_handler(signalsManagerQObjPointer, p0, "receiveSignal") 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() = proc mainProc() =
if defined(macosx) and defined(production): if defined(macosx) and defined(production):
@ -133,6 +139,7 @@ proc mainProc() =
defer: defer:
info "shutting down..." info "shutting down..."
signalsManagerQObjPointer = nil signalsManagerQObjPointer = nil
keycardServiceQObjPointer = nil
isProductionQVariant.delete() isProductionQVariant.delete()
isExperimentalQVariant.delete() isExperimentalQVariant.delete()
signalsManagerQVariant.delete() signalsManagerQVariant.delete()
@ -149,6 +156,12 @@ proc mainProc() =
info "Terminating the app as the second instance" info "Terminating the app as the second instance"
quit() 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("Version: {DESKTOP_VERSION}")
info fmt("Commit: {GIT_COMMIT}") info fmt("Commit: {GIT_COMMIT}")
info "Current date:", currentDateTime=now() info "Current date:", currentDateTime=now()
@ -156,11 +169,6 @@ proc mainProc() =
info "starting application controller..." info "starting application controller..."
appController.start() 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..." info "starting application..."
app.exec() app.exec()

View File

@ -31,41 +31,86 @@ OnboardingBasePage {
{ {
return allowNotificationsViewComponent return allowNotificationsViewComponent
} }
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcome) if (root.startupStore.currentStartupState.stateType === Constants.startupState.welcome)
{ {
return welcomeViewComponent 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.welcomeOldStatusUser ||
root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileImportSeedPhrase) root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileImportSeedPhrase)
{ {
return keysMainViewComponent 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) root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileChatKey)
{ {
return insertDetailsViewComponent return insertDetailsViewComponent
} }
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreatePassword) if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileCreatePassword)
{ {
return createPasswordViewComponent return createPasswordViewComponent
} }
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileConfirmPassword) if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileConfirmPassword)
{ {
return confirmPasswordViewComponent return confirmPasswordViewComponent
} }
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.biometrics) if (root.startupStore.currentStartupState.stateType === Constants.startupState.biometrics)
{ {
return touchIdAuthViewComponent return touchIdAuthViewComponent
} }
else if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileEnterSeedPhrase) if (root.startupStore.currentStartupState.stateType === Constants.startupState.userProfileEnterSeedPhrase)
{ {
return seedPhraseInputViewComponent 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 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 return undefined
} }
@ -87,7 +132,9 @@ OnboardingBasePage {
onAccountImportError: { onAccountImportError: {
if (error === Constants.existingAccountError) { if (error === Constants.existingAccountError) {
msgDialog.title = qsTr("Keys for this account already exist") 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 { } else {
msgDialog.title = qsTr("Error importing seed") msgDialog.title = qsTr("Error importing seed")
msgDialog.text = error msgDialog.text = error
@ -169,4 +216,46 @@ OnboardingBasePage {
startupStore: root.startupStore 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 string colorId: ""
property var colorHash property var colorHash
property url image: "" property url image: ""
property bool keycardCreatedAccount: false
property StatusIconSettings iconSettings: StatusIconSettings { property StatusIconSettings iconSettings: StatusIconSettings {
name: "add" name: "add"
} }
@ -65,8 +66,28 @@ MenuItem {
font.pixelSize: 15 font.pixelSize: 15
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: userImageOrIcon.right 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 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 1.0
import shared.controls 1.0 import shared.controls 1.0
import shared.keycard 1.0
StatusModal { StatusModal {
property bool firstPINFieldValid: false property bool firstPINFieldValid: false

View File

@ -35,6 +35,10 @@ QtObject {
return root.startupModuleInst.generateImage(source, aX, aY, bX, bY) return root.startupModuleInst.generateImage(source, aX, aY, bX, bY)
} }
function getCroppedProfileImage() {
return root.startupModuleInst.getCroppedProfileImage()
}
function setDisplayName(value) { function setDisplayName(value) {
root.startupModuleInst.setDisplayName(value) root.startupModuleInst.setDisplayName(value)
} }
@ -51,6 +55,18 @@ QtObject {
return root.startupModuleInst.getPassword() 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) { function getPasswordStrengthScore(password) {
let userName = root.startupModuleInst.importedAccountAlias let userName = root.startupModuleInst.importedAccountAlias
return root.startupModuleInst.getPasswordStrengthScore(password, userName) return root.startupModuleInst.getPasswordStrengthScore(password, userName)
@ -63,4 +79,12 @@ QtObject {
function setSelectedLoginAccountByIndex(index) { function setSelectedLoginAccountByIndex(index) {
root.startupModuleInst.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.text = root.startupStore.getDisplayName();
nameInput.input.edit.forceActiveFocus(); nameInput.input.edit.forceActiveFocus();
userImage.image.source = root.startupStore.getCroppedProfileImage();
} }
Loader { 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 id: keycardLink
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: Theme.palette.primaryColor1 color: Theme.palette.primaryColor1
font.pixelSize: 15
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@ -148,10 +149,10 @@ Item {
//TODO remove when sync code is implemented //TODO remove when sync code is implemented
opacity: 0.0 opacity: 0.0
} }
// PropertyChanges { PropertyChanges {
// target: keycardLink target: keycardLink
// text: qsTr("Login with Keycard") text: qsTr("Login with Keycard")
// } }
PropertyChanges { PropertyChanges {
target: seedLink target: seedLink
text: qsTr("Enter a seed phrase") text: qsTr("Enter a seed phrase")
@ -179,10 +180,10 @@ Item {
//TODO remove when sync code is implemented //TODO remove when sync code is implemented
opacity: 1.0 opacity: 1.0
} }
// PropertyChanges { PropertyChanges {
// target: keycardLink target: keycardLink
// text: qsTr("Generate keys for a new Keycard") text: qsTr("Generate keys for a new Keycard")
// } }
PropertyChanges { PropertyChanges {
target: seedLink target: seedLink
text: qsTr("Import a seed phrase") text: qsTr("Import a seed phrase")
@ -217,10 +218,10 @@ Item {
//TODO remove when sync code is implemented //TODO remove when sync code is implemented
opacity: 1.0 opacity: 1.0
} }
// PropertyChanges { PropertyChanges {
// target: keycardLink target: keycardLink
// text: qsTr("Import a seed phrase into a new Keycard") text: qsTr("Import a seed phrase into a new Keycard")
// } }
PropertyChanges { PropertyChanges {
target: seedLink target: seedLink
text: "" text: ""

File diff suppressed because it is too large Load Diff

View File

@ -277,9 +277,22 @@ Item {
function checkMnemonicLength() { function checkMnemonicLength() {
submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex]) submitButton.enabled = (root.mnemonicInput.length === root.tabs[switchTabBar.currentIndex])
} }
text: root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase? text: {
qsTr("Import") : if (root.startupStore.currentStartupState.flowType === Constants.startupFlow.firstRunNewUserImportSeedPhrase) {
qsTr("Restore Status Profile") 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: { onClicked: {
let mnemonicString = ""; let mnemonicString = "";
var sortTable = mnemonicInput.sort(function (a, b) { 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.top: txtTitle.bottom
anchors.topMargin: Style.current.padding anchors.topMargin: Style.current.padding
color: Style.current.secondaryText 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 horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 15 font.pixelSize: 15
@ -86,7 +90,11 @@ Item {
objectName: "touchIdIPreferToUseMyPasswordText" objectName: "touchIdIPreferToUseMyPasswordText"
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: Theme.palette.primaryColor1 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 font.pixelSize: 15
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent

View File

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

View File

@ -31,7 +31,6 @@ QtObject {
readonly property string nodeManagement: "nodeManagement" readonly property string nodeManagement: "nodeManagement"
readonly property string onlineUsers: "onlineUsers" readonly property string onlineUsers: "onlineUsers"
readonly property string gifWidget: "gifWidget" readonly property string gifWidget: "gifWidget"
readonly property string keycard: "keycard"
readonly property string communityHistoryArchiveSupport: "communityHistoryArchiveSupport" readonly property string communityHistoryArchiveSupport: "communityHistoryArchiveSupport"
readonly property string communitiesPortal: "communitiesPortal" readonly property string communitiesPortal: "communitiesPortal"
} }
@ -131,9 +130,5 @@ QtObject {
else if (feature === experimentalFeatures.gifWidget) { else if (feature === experimentalFeatures.gifWidget) {
localAccountSensitiveSettings.isGifWidgetEnabled = !localAccountSensitiveSettings.isGifWidgetEnabled 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 { StatusSectionHeadline {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right

View File

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