feat(onboarding / privacy): Integrate password validation (zxcvbn lib) in new password screens

Use new `PrivacyStore` method getPasswordStrengthScore and link it to the new password strength bar value.

Used backend/general to call to `status-go` method and services/general to define the common `GetPasswordStrengthScore` service.

Added onboarding chain to get password strength score information from `OnboardingStore` to `status-go` call.

Closes #5096
This commit is contained in:
Noelia 2022-03-22 10:29:59 +01:00 committed by Noelia
parent d05bd6ce08
commit 8f996992b2
22 changed files with 104 additions and 85 deletions

View File

@ -185,7 +185,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result,
statusFoundation.events,
result.keychainService,
result.accountsService
result.accountsService,
result.generalService
)
result.mainModule = main_module.newModule[AppController](
result,
@ -219,6 +220,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.gifService,
result.ensService,
result.networkService,
result.generalService
)
# Do connections

View File

@ -49,6 +49,7 @@ import ../../../app_service/service/mailservers/service as mailservers_service
import ../../../app_service/service/gif/service as gif_service
import ../../../app_service/service/ens/service as ens_service
import ../../../app_service/service/network/service as network_service
import ../../../app_service/service/general/service as general_service
import ../../core/notifications/details
import ../../core/eventemitter
@ -108,6 +109,7 @@ proc newModule*[T](
gifService: gif_service.Service,
ensService: ens_service.Service,
networkService: network_service.Service,
generalService: general_service.Service
): Module[T] =
result = Module[T]()
result.delegate = delegate
@ -144,7 +146,7 @@ proc newModule*[T](
result.profileSectionModule = profile_section_module.newModule(
result, events, accountsService, settingsService, stickersService,
profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService,
devicesService, mailserversService, chatService, ensService, walletAccountService,
devicesService, mailserversService, chatService, ensService, walletAccountService, generalService
)
result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService)
result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService,

View File

@ -17,6 +17,7 @@ import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/stickers/service as stickersService
import ../../../../app_service/service/ens/service as ens_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../app_service/service/general/service as general_service
import ./profile/module as profile_module
import ./contacts/module as contacts_module
@ -66,6 +67,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
chatService: chat_service.Service,
ensService: ens_service.Service,
walletAccountService: wallet_account_service.Service,
generalService: general_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -77,7 +79,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
result.profileModule = profile_module.newModule(result, profileService)
result.contactsModule = contacts_module.newModule(result, events, contactsService)
result.languageModule = language_module.newModule(result, languageService)
result.privacyModule = privacy_module.newModule(result, events, settingsService, privacyService)
result.privacyModule = privacy_module.newModule(result, events, settingsService, privacyService, generalService)
result.aboutModule = about_module.newModule(result, events, aboutService)
result.advancedModule = advanced_module.newModule(result, events, settingsService, stickersService, nodeConfigurationService)
result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService)

View File

@ -3,6 +3,7 @@ import io_interface
import ../../../../core/eventemitter
import ../../../../../app_service/service/settings/service as settings_service
import ../../../../../app_service/service/privacy/service as privacy_service
import ../../../../../app_service/service/general/service as general_service
type
@ -11,15 +12,19 @@ type
events: EventEmitter
settingsService: settings_service.Service
privacyService: privacy_service.Service
generalService: general_service.Service
proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter,
settingsService: settings_service.Service,
privacyService: privacy_service.Service): Controller =
privacyService: privacy_service.Service,
generalService: general_service.Service): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.settingsService = settingsService
result.privacyService = privacyService
result.generalService = generalService
proc delete*(self: Controller) =
discard
@ -71,5 +76,5 @@ proc getProfilePicturesVisibility*(self: Controller): int =
proc setProfilePicturesVisibility*(self: Controller, value: int): bool =
self.settingsService.saveProfilePicturesVisibility(value)
proc getPasswordStrengthScore*(self: Controller, password: string): int =
return self.privacyService.getPasswordStrengthScore(password)
method getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)

View File

@ -1,11 +1,14 @@
import NimQml, chronicles
import ../../../../../app/global/global_singleton
import ./io_interface, ./view, ./controller
import ../io_interface as delegate_interface
import ../../../../core/eventemitter
import ../../../../../app_service/service/settings/service as settings_service
import ../../../../../app_service/service/privacy/service as privacy_service
import ../../../../../app_service/service/general/service as general_service
export io_interface
@ -19,13 +22,14 @@ type
proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter,
settingsService: settings_service.Service,
privacyService: privacy_service.Service):
privacyService: privacy_service.Service,
generalService: general_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, settingsService, privacyService)
result.controller = controller.newController(result, events, settingsService, privacyService, generalService)
result.moduleLoaded = false
method delete*(self: Module) =
@ -94,4 +98,4 @@ method setProfilePicturesVisibility*(self: Module, value: int) =
self.view.profilePicturesVisibilityChanged()
method getPasswordStrengthScore*(self: Module, password: string): int =
return self.controller.getPasswordStrengthScore(password)
return self.controller.getPasswordStrengthScore(password, singletonInstance.userProfile.getUsername())

View File

@ -9,6 +9,7 @@ import login/module as login_module
import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/accounts/service as accounts_service
import ../../../app_service/service/general/service as general_service
export io_interface
@ -24,7 +25,8 @@ type
proc newModule*[T](delegate: T,
events: EventEmitter,
keychainService: keychain_service.Service,
accountsService: accounts_service.Service):
accountsService: accounts_service.Service,
generalService: general_service.Service):
Module[T] =
result = Module[T]()
result.delegate = delegate
@ -33,7 +35,7 @@ proc newModule*[T](delegate: T,
result.controller = controller.newController(result, events, accountsService)
# Submodules
result.onboardingModule = onboarding_module.newModule(result, events, accountsService)
result.onboardingModule = onboarding_module.newModule(result, events, accountsService, generalService)
result.loginModule = login_module.newModule(result, events, keychainService,
accountsService)

View File

@ -5,6 +5,7 @@ import io_interface
import ../../../core/signals/types
import ../../../core/eventemitter
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/general/service as general_service
logScope:
topics = "onboarding-controller"
@ -14,17 +15,20 @@ type
delegate: io_interface.AccessInterface
events: EventEmitter
accountsService: accounts_service.Service
generalService: general_service.Service
selectedAccountId: string
displayName: string
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
accountsService: accounts_service.Service):
accountsService: accounts_service.Service,
generalService: general_service.Service):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.accountsService = accountsService
result.generalService = generalService
proc delete*(self: Controller) =
discard
@ -62,3 +66,6 @@ proc importMnemonic*(self: Controller, mnemonic: string) =
else:
self.delegate.importAccountError()
method getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)

View File

@ -43,3 +43,6 @@ method importMnemonic*(self: AccessInterface, mnemonic: string) {.base.} =
method setDisplayName*(self: AccessInterface, displayName: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getPasswordStrengthScore*(self: AccessInterface, password: string, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -5,6 +5,7 @@ import view, controller, item
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/general/service as general_service
export io_interface
@ -17,13 +18,14 @@ type
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter,
accountsService: accounts_service.Service):
accountsService: accounts_service.Service,
generalService: general_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, accountsService)
result.controller = controller.newController(result, events, accountsService, generalService)
result.moduleLoaded = false
method delete*(self: Module) =
@ -76,3 +78,6 @@ method importAccountError*(self: Module) =
method importAccountSuccess*(self: Module) =
self.view.importAccountSuccess()
method getPasswordStrengthScore*(self: Module, password, userName: string): int =
return self.controller.getPasswordStrengthScore(password, userName)

View File

@ -89,3 +89,6 @@ QtObject:
proc importAccountSuccess*(self: View) =
self.importedAccountChanged()
proc getPasswordStrengthScore*(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName)

View File

@ -39,3 +39,15 @@ proc startMessenger*(self: Service) =
let errDesription = e.msg
error "error: ", errDesription
return
method getPasswordStrengthScore*(self: Service, password, userName: string): int =
try:
let response = status_general.getPasswordStrengthScore(password, @[userName])
if(response.result.contains("error")):
let errMsg = response.result["error"].getStr()
error "error: ", methodName="getPasswordStrengthScore", errDesription = errMsg
return
return response.result["score"].getInt()
except Exception as e:
error "error: ", methodName="getPasswordStrengthScore", errName = e.name, errDesription = e.msg

View File

@ -1,6 +1,5 @@
import NimQml, json, strutils, chronicles
import ../../../app/global/global_singleton
import ../settings/service as settings_service
import ../accounts/service as accounts_service
@ -150,16 +149,3 @@ QtObject:
except Exception as e:
error "error: ", procName="validatePassword", errName = e.name, errDesription = e.msg
return false
proc getPasswordStrengthScore*(self: Service, password: string): int =
try:
let userName = singletonInstance.userProfile.getUsername()
let response = status_privacy.getPasswordStrength(password, @[userName])
if(response.result.contains("error")):
let errMsg = response.result["error"].getStr()
error "error: ", procName="getPasswordStrengthScore", errDesription = errMsg
return
return response.result["Score"].getInt()
except Exception as e:
error "error: ", procName="getPasswordStrengthScore", errName = e.name, errDesription = e.msg

View File

@ -45,3 +45,12 @@ proc dropPeerByID*(peer: string): RpcResponse[JsonNode] {.raises: [Exception].}
proc removePeer*(peer: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [peer]
result = core.callPrivateRPC("admin_removePeer", payload)
proc getPasswordStrengthScore*(password: string, userInputs: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} =
let params = %* {"password": password, "userInputs": userInputs}
try:
let response = status_go.getPasswordStrengthScore($(params))
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error", methodName = "getPasswordStrengthScore", exception=e.msg
raise newException(RpcException, e.msg)

View File

@ -23,12 +23,3 @@ proc changeDatabasePassword*(keyUID: string, password: string, newPassword: stri
proc getLinkPreviewWhitelist*(): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* []
result = callPrivateRPC("getLinkPreviewWhitelist".prefix, payload)
proc getPasswordStrength*(password: string, userInputs: seq[string]): RpcResponse[JsonNode] {.raises: [Exception].} =
let params = %* {"password": password, "userInputs": userInputs}
try:
let response = status_go.getPasswordStrength($(params))
result.result = Json.decode(response, JsonNode)
except RpcException as e:
error "error", methodName = "getPasswordStrength", exception=e.msg
raise newException(RpcException, e.msg)

View File

@ -246,6 +246,7 @@ QtObject {
property var createPasswordComponent: Component {
id: createPassword
CreatePasswordView {
store: OnboardingStore
newPassword: d.newPassword
confirmationPassword: d.confirmationPassword

View File

@ -14,6 +14,11 @@ QtObject {
onBoardingModul.setSelectedAccountByIndex(selectedAccountIdx)
}
function getPasswordStrengthScore(password) {
let userName = onBoardingModul.importedAccountAlias
return onBoardingModul.getPasswordStrengthScore(password, userName)
}
property ListModel accountsSampleData: ListModel {
ListElement {
username: "Ferocious Herringbone Sinewave2"

View File

@ -77,7 +77,7 @@ Page {
qsTr("Connecting...") :
qsTr("Confirm you password (again)")
textField.echoMode: showPassword ? TextInput.Normal : TextInput.Password
textField.validator: RegExpValidator { regExp: /^[!-~]+$/ } // That incudes NOT extended ASCII printable characters less space
textField.validator: RegExpValidator { regExp: /^[!-~]{0,64}$/ } // That incudes NOT extended ASCII printable characters less space and a maximum of 64 characters allowed
keepHeight: true
textField.rightPadding: showHideCurrentIcon.width + showHideCurrentIcon.anchors.rightMargin + Style.current.padding / 2

View File

@ -12,22 +12,24 @@ import "../../Profile/views"
Page {
id: root
property var store
property string newPassword
property string confirmationPassword
signal passwordCreated(string newPassword, string confirmationPassword)
signal backClicked()
Component.onCompleted: { view.forceNewPswInputFocus() }
anchors.fill: parent
background: null
Component.onCompleted: { view.forceNewPswInputFocus() }
QtObject {
id: d
readonly property int zBehind: 1
readonly property int zFront: 100
}
anchors.fill: parent
Column {
spacing: 4 * Style.current.padding
anchors.centerIn: parent
@ -35,6 +37,7 @@ Page {
PasswordView {
id: view
store: root.store
newPswText: root.newPassword
confirmationPswText: root.confirmationPassword
}

View File

@ -45,6 +45,7 @@ StatusModal {
PasswordView {
id: view
store: root.privacyStore
anchors.topMargin: Style.current.padding
anchors.centerIn: parent
titleVisible: false

View File

@ -13,6 +13,7 @@ import StatusQ.Components 0.1
Column {
id: root
property var store
property bool ready: newPswInput.text.length >= root.minPswLen && newPswInput.text === confirmPswInput.text && errorTxt.text === ""
property int minPswLen: 6
property bool createNewPsw: true
@ -21,6 +22,7 @@ Column {
property string introText: qsTr("Create a password to unlock Status on this device & sign transactions.")
property string recoverText: qsTr("You will not be able to recover this password if it is lost.")
property string strengthenText: qsTr("Minimum 6 characers. To strengthen your password consider including:")
readonly property int zBehind: 1
readonly property int zFront: 100
@ -62,7 +64,7 @@ Column {
property bool containsNumbers: false
property bool containsSymbols: false
readonly property var validator: RegExpValidator { regExp: /^[!-~]+$/ } // That incudes NOT extended ASCII printable characters less space
readonly property var validator: RegExpValidator { regExp: /^[!-~]{0,64}$/ } // That incudes NOT extended ASCII printable characters less space and a maximum of 64 characters allowed
// Password strength categorization / validation
function lowerCaseValidator(text) { return (/[a-z]/.test(text)) }
@ -70,46 +72,20 @@ Column {
function numbersValidator(text) { return (/\d/.test(text)) }
// That incudes NOT extended ASCII printable symbols less space:
function symbolsValidator(text) { return (/[!-\/:-@[-`{-~]/.test(text)) }
function findUniqueChars(text) {
// The variable that contains the unique values
let uniq = "";
for(let i = 0; i < text.length; i++) {
// Checking if the uniq contains the character
if(uniq.includes(text[i]) === false) {
// If the character not present in uniq
// Concatenate the character with uniq
uniq += text[i]
// Used to convert strength from a given score to a specific category
function convertStrength(score) {
var strength = StatusPasswordStrengthIndicator.Strength.None
switch(score) {
case 0: strength = StatusPasswordStrengthIndicator.Strength.VeryWeak; break
case 1: strength = StatusPasswordStrengthIndicator.Strength.Weak; break
case 2: strength = StatusPasswordStrengthIndicator.Strength.SoSo; break
case 3: strength = StatusPasswordStrengthIndicator.Strength.Good; break
case 4: strength = StatusPasswordStrengthIndicator.Strength.Great; break
}
}
return uniq
}
// Algorithm defined in functional requirements / Password categorization
function getPswStrength() {
let rules = 0
let points = 0
let strengthType = StatusPasswordStrengthIndicator.Strength.None
if(newPswInput.text.length >= root.minPswLen) { points += 10; rules++ }
if(d.containsLower) { points += 5; rules++ }
if(d.containsUpper) { points += 5; rules++ }
if(d.containsNumbers) { points += 5; rules++ }
if(d.containsSymbols) { points += 10; rules++ }
let uniq = d.findUniqueChars(newPswInput.text)
if(uniq.length >= 5) { points += 5; rules++ }
// Update points according to rules used:
points += rules * 10/*factor*/
// Strength decision taken:
if(points > 0 && points < 40) strengthType = StatusPasswordStrengthIndicator.Strength.VeryWeak
else if(points >= 40 && points < 60) strengthType = StatusPasswordStrengthIndicator.Strength.Weak
else if(points >= 60 && points < 80) strengthType = StatusPasswordStrengthIndicator.Strength.SoSo
else if(points >= 80 && points < 100) strengthType = StatusPasswordStrengthIndicator.Strength.Good
else if(points >= 100) strengthType = StatusPasswordStrengthIndicator.Strength.Great
return strengthType
if(strength > 4)
strength = StatusPasswordStrengthIndicator.Strength.Great
return strength
}
// Password validation / error message selection:
@ -232,7 +208,7 @@ Column {
d.containsSymbols = d.symbolsValidator(text)
// Update strength indicator:
strengthInditactor.strength = d.getPswStrength()
strengthInditactor.strength = d.convertStrength(root.store.getPasswordStrengthScore(newPswInput.text))
}
StatusFlatRoundButton {

@ -1 +1 @@
Subproject commit ed2b3f656732e8670a21150559705933fdc8342a
Subproject commit a46e18940fbd8dd338f248429d2e3b02dd387cc2

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit cbe2560d928603057a21adebd6f5540195247ee6
Subproject commit e67592d556aa7320ee660ea64e2bfccfbc5166e9