Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling biometry without password during sync #17960

Merged
merged 20 commits into from
Nov 30, 2023

Conversation

clauxx
Copy link
Member

@clauxx clauxx commented Nov 21, 2023

fixes #17928

Summary

Currently, when the user syncs device A with device B (mobile), the user is prompted to input the device A password to enable biometry. This was needed because the device A password comes hashed during syncing, while for biometry authentication we need the password in plaintext for the keychain.

As agreed with @cammellos and @Samyoul, we should be moving away from storing the plaintext password in the keychain in favor of storing it already hashed (we only need it hashed anyway). As such, this PR:

  • adds a migration for the keychain password from plaintext to hashed form
  • removes the password authentication step for enabling biometry from sync onboarding

Testing notes

To test the migration:

  1. create an account with enabled biometry (don't use this branch yet)
  2. install this PR build
  3. try to log in using biometry

If it fails to log in during step 3, then there's something wrong with the migration.

Platforms

  • Android
  • iOS

Areas that maybe impacted

Functional
  • onboarding
  • biometry auth

Steps to test

  • start sync process on another device e.g. status-desktop
  • open status-mobile
  • start sync (by scanning the QR code or entering the sync code)
  • when biometry is prompted, enable it and register it

When enabling biometry, the password auth should not appear and the user should be able to log it again with biometry after the app was closed.

status: ready

@clauxx clauxx self-assigned this Nov 21, 2023
@status-im-auto
Copy link
Member

status-im-auto commented Nov 21, 2023

Jenkins Builds

Click to see older builds (60)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 742e3d8 #1 2023-11-21 18:36:08 ~7 min android 🤖apk 📲
742e3d8 #1 2023-11-21 18:36:48 ~8 min tests 📄log
✔️ 742e3d8 #1 2023-11-21 18:39:45 ~11 min android-e2e 🤖apk 📲
✔️ 742e3d8 #1 2023-11-21 18:40:21 ~11 min ios 📱ipa 📲
✔️ ec5e459 #2 2023-11-21 19:54:48 ~6 min android-e2e 🤖apk 📲
✔️ ec5e459 #2 2023-11-21 19:54:49 ~6 min android 🤖apk 📲
✔️ ec5e459 #2 2023-11-21 19:59:42 ~11 min tests 📄log
✔️ ec5e459 #2 2023-11-21 20:00:14 ~11 min ios 📱ipa 📲
✔️ ae6074b #3 2023-11-21 20:23:22 ~6 min android 🤖apk 📲
✔️ ae6074b #3 2023-11-21 20:23:46 ~6 min android-e2e 🤖apk 📲
✔️ ae6074b #3 2023-11-21 20:27:37 ~10 min tests 📄log
✔️ ae6074b #3 2023-11-21 20:28:19 ~11 min ios 📱ipa 📲
29f3f60 #4 2023-11-22 14:01:41 ~2 min tests 📄log
✔️ 29f3f60 #4 2023-11-22 14:05:55 ~6 min android-e2e 🤖apk 📲
✔️ 29f3f60 #4 2023-11-22 14:06:05 ~6 min android 🤖apk 📲
✔️ 29f3f60 #4 2023-11-22 14:11:10 ~11 min ios 📱ipa 📲
2054a48 #5 2023-11-22 15:03:13 ~2 min tests 📄log
✔️ 2054a48 #5 2023-11-22 15:06:54 ~6 min android-e2e 🤖apk 📲
✔️ 2054a48 #5 2023-11-22 15:07:08 ~6 min ios 📱ipa 📲
✔️ 2054a48 #5 2023-11-22 15:08:40 ~7 min android 🤖apk 📲
024ff0f #6 2023-11-22 16:53:49 ~2 min tests 📄log
✔️ 024ff0f #6 2023-11-22 16:57:32 ~6 min ios 📱ipa 📲
✔️ 024ff0f #6 2023-11-22 16:57:43 ~6 min android-e2e 🤖apk 📲
✔️ 024ff0f #6 2023-11-22 16:57:51 ~6 min android 🤖apk 📲
cc81673 #7 2023-11-22 18:11:26 ~2 min tests 📄log
✔️ cc81673 #7 2023-11-22 18:15:47 ~6 min ios 📱ipa 📲
✔️ cc81673 #7 2023-11-22 18:16:02 ~6 min android-e2e 🤖apk 📲
✔️ cc81673 #7 2023-11-22 18:16:07 ~6 min android 🤖apk 📲
✔️ f991ac6 #8 2023-11-22 18:31:15 ~6 min android-e2e 🤖apk 📲
✔️ f991ac6 #8 2023-11-22 18:31:24 ~6 min android 🤖apk 📲
✔️ f991ac6 #8 2023-11-22 18:31:47 ~6 min ios 📱ipa 📲
✔️ f991ac6 #8 2023-11-22 18:35:33 ~10 min tests 📄log
✔️ 3e9ce16 #10 2023-11-22 21:55:45 ~6 min android-e2e 🤖apk 📲
✔️ 3e9ce16 #10 2023-11-22 21:56:10 ~6 min ios 📱ipa 📲
✔️ 3e9ce16 #10 2023-11-22 22:01:58 ~12 min tests 📄log
✔️ 3e9ce16 #10 2023-11-22 22:02:03 ~12 min android 🤖apk 📲
✔️ adbc3e8 #11 2023-11-24 13:26:53 ~6 min ios 📱ipa 📲
✔️ adbc3e8 #12 2023-11-24 14:54:41 ~9 min android 🤖apk 📲
✔️ adbc3e8 #12 2023-11-24 14:55:44 ~9 min android-e2e 🤖apk 📲
✔️ adbc3e8 #12 2023-11-24 15:00:25 ~14 min tests 📄log
5093a4e #13 2023-11-27 13:31:29 ~4 min tests 📄log
✔️ 5093a4e #13 2023-11-27 13:33:08 ~6 min android 🤖apk 📲
✔️ 5093a4e #13 2023-11-27 13:33:11 ~6 min android-e2e 🤖apk 📲
✔️ 5093a4e #12 2023-11-27 13:33:27 ~6 min ios 📱ipa 📲
✔️ ae7d3e2 #13 2023-11-27 13:39:41 ~6 min ios 📱ipa 📲
✔️ ae7d3e2 #14 2023-11-27 13:39:53 ~6 min android-e2e 🤖apk 📲
✔️ ae7d3e2 #14 2023-11-27 13:41:24 ~7 min android 🤖apk 📲
✔️ ae7d3e2 #14 2023-11-27 13:43:35 ~9 min tests 📄log
1724b16 #15 2023-11-28 13:22:24 ~2 min tests 📄log
✔️ 1724b16 #15 2023-11-28 13:25:36 ~5 min android-e2e 🤖apk 📲
✔️ 1724b16 #15 2023-11-28 13:25:58 ~6 min android 🤖apk 📲
✔️ 1724b16 #14 2023-11-28 13:31:07 ~11 min ios 📱ipa 📲
10b0bd7 #17 2023-11-29 10:19:39 ~2 min tests 📄log
✔️ 10b0bd7 #17 2023-11-29 10:23:03 ~6 min android 🤖apk 📲
✔️ 10b0bd7 #17 2023-11-29 10:23:10 ~6 min android-e2e 🤖apk 📲
✔️ 10b0bd7 #16 2023-11-29 10:23:44 ~6 min ios 📱ipa 📲
326db65 #18 2023-11-29 20:02:03 ~2 min tests 📄log
✔️ 326db65 #18 2023-11-29 20:05:50 ~6 min android-e2e 🤖apk 📲
✔️ 326db65 #18 2023-11-29 20:05:58 ~6 min android 🤖apk 📲
✔️ 326db65 #17 2023-11-29 20:06:51 ~7 min ios 📱ipa 📲
Commit #️⃣ Finished (UTC) Duration Platform Result
721aee0 #19 2023-11-30 11:01:43 ~2 min tests 📄log
✔️ 721aee0 #19 2023-11-30 11:05:00 ~6 min android-e2e 🤖apk 📲
✔️ 721aee0 #18 2023-11-30 11:05:17 ~6 min ios 📱ipa 📲
✔️ 721aee0 #19 2023-11-30 11:05:31 ~6 min android 🤖apk 📲
✔️ 331f2cb #19 2023-11-30 11:16:35 ~6 min ios 📱ipa 📲
✔️ 331f2cb #20 2023-11-30 11:16:46 ~6 min android-e2e 🤖apk 📲
✔️ 331f2cb #20 2023-11-30 11:16:53 ~6 min android 🤖apk 📲
✔️ 331f2cb #20 2023-11-30 11:20:10 ~9 min tests 📄log

Copy link
Contributor

@cammellos cammellos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me, I am not the most familiar with the current practices and standard of the codebase, so maybe someone more up to date should have a look, but otherwise looks great to me.
Great work!

[key-uid callback]
(keychain/get-credentials key-uid
#(if %
(callback (security/mask-data (oops/oget % "password")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

threading maybe makes it a bit more readable:

(-> (oops/get % password)
     (security/mask-data)
     (callback))

or

(as-> % $
   (oops/oget $ "password")
   (security/mask-data $)
   (callback $))

also, you can have the if inside, since you are calling callback regardless:

(callback (when %
                  (-> (oops/get...))

maybe there's also better ways, but it's all fine to be honest

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

went with the last option

[key-uid callback]
(keychain/get-credentials
(password-migration-key-name key-uid)
#(callback (boolean %))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(comp callback boolean) is equivalent if you prefer

(.then #(save-user-password! key-uid %))
(.then #(save-password-migration! key-uid))
(.then #(callback))
(.catch #(log/error "Failed to migrate the keychain password for " key-uid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilmotta suggested some structured logging format, maybe there's a case for it to be used here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right @cammellos, log/error can take pretty much anything, and the lib allows us to perform transformations with middlewares. The convention we try to follow in the repo is to pass a first argument string with a concise humanized message, and then the second argument a map giving enough context for the future dev to make sense of the error.

So here it could be:

(.catch (fn [error]
          (log/error "Failed to migrate keychain password"
                     {:key-uid key-uid
                      :event   :keychain/password-hash-migration
                      :error   error})))

The original code concatenating values won't be as clear. It will output something like Failed to migrate the keychain password for foo, but what is foo? Using a data structure the developer will know exactly what it is.

Copy link
Member Author

@clauxx clauxx Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, had no idea we had a convention for this! Fixed it for the keychain.events namespace.

@@ -151,12 +162,16 @@

(rf/defn get-auth-method-success
{:events [:profile.login/get-auth-method-success]}
[{:keys [db]} auth-method]
[{:keys [db] :as cofx} auth-method key-uid]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see cofx being used here, maybe I am missing something though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't used and found another unused one in the same file

src/utils/security/core.cljs Show resolved Hide resolved
Comment on lines 153 to 157
(-> (get-user-password! key-uid identity)
(.then #(security/hash-masked-password %))
(.then #(save-user-password! key-uid %))
(.then #(save-password-migration! key-uid))
(.then #(callback))
Copy link
Member Author

@clauxx clauxx Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cammellos @ilmotta I stumbled on the promesa package that has some really nice utilities for working with promises. It would be good for cases like this, where the snippet could be simplified to smth like:

(p/->> (get-user-password! key-uid identity)
       (security/hash-masked-password)
       (save-user-password! key-uid)
       ...)

Do you think it would be a good addition to the project?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clauxx, I'm probably biased in favor of promesa because I extensively used it in the past. The library has a very solid API, so it's like one of those libs in Clojure where you add to the project and profit without maintenance burden.

OTOH, I used promesa in projects that heavily used promises everywhere (Node.js runtime instead of browser). Every code path had promises, so promesa paid off. In status-mobile we mostly have a few promises in re-frame effects (thankfully 😃). Therefore, our usages are very linear and simple, and I don't see promesa adding that much value.

I have also noticed promises code in status-mobile is quite convoluted and show some lack of knowledge of how promises work. Namespaces like react-native.cameraroll and react-native.permissions, among others show that to me because they confusingly mix callbacks with promises, probably an attempt to hide promises.

If we add promesa on top, I think it will be nice for some, but another layer on top of promises that some are not willing to learn.

In the end, I vote for adding it if you want in a separate PR, but better check with more devs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw the same issue in the react-native.keychain namespace, which is a bit annoying. Found myself pass identity as the callback in several places to make use of the resolved promise values. I'd say the value would be in having a consistent way of dealing with promises across the project and getting rid of the callbacks entirely, but this could be done without the library by just adding it in the guideline.

(defn get-user-password!
[key-uid callback]
(keychain/get-credentials key-uid
#(callback (when %
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the opinion, but also the impression from my experience with Clojure that the shorthand syntax #() should be used mostly for very simple functions, usually not spanning multiple lines. In this PR I see a lot of usages of this shorthand syntax in places where we (in status-mobile) wouldn't normally use. Hence, my feedback here is to ask you to be mindful of the shortcomings and lean on the side of clarity.

In the name of saving just a few characters we have a couple downsides:

  1. Obscures the argument with %. % means nothing, so the developer is forced to read the surrounding context to guess what it is.
  2. The developer can't be sure if the function is using any of the arguments without hunting for percent signs. This is often important. Contrast that with named arguments, where the linter will tell if the argument is used or not and it's immediately apparent to the developer.
  3. It makes debugging a little bit less enjoyable, because #() doesn't allow multiple forms without wrapping in another macro like do or let.
  4. It obscures the function signature. If the function has two or more arguments, %1 and %2 can be difficult to understand, except on core functions where devs have memorized the arities, like reduce.

I have read some Clojure projects even outright banned #(), but I like it for simple one-liners.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Don't remember reading this in the guideline, but not a fan of #() either as it's tough on the eyes 🗡️ . Will keep an eye for this when refactoring in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not in the guidelines yet, but it would be useful if anyone is brave ;)

key-uid
#(when-not %
(log/error
(str "Error while setting up keychain migration")))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess str here is a leftover when there were more arguments to concatenate, but it can be dropped now.

(.then #(save-user-password! key-uid %))
(.then #(save-password-migration! key-uid))
(.then #(callback))
(.catch #(log/error "Failed to migrate the keychain password for " key-uid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right @cammellos, log/error can take pretty much anything, and the lib allows us to perform transformations with middlewares. The convention we try to follow in the repo is to pass a first argument string with a concise humanized message, and then the second argument a map giving enough context for the future dev to make sense of the error.

So here it could be:

(.catch (fn [error]
          (log/error "Failed to migrate keychain password"
                     {:key-uid key-uid
                      :event   :keychain/password-hash-migration
                      :error   error})))

The original code concatenating values won't be as clear. It will output something like Failed to migrate the keychain password for foo, but what is foo? Using a data structure the developer will know exactly what it is.

Comment on lines 153 to 157
(-> (get-user-password! key-uid identity)
(.then #(security/hash-masked-password %))
(.then #(save-user-password! key-uid %))
(.then #(save-password-migration! key-uid))
(.then #(callback))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clauxx, I'm probably biased in favor of promesa because I extensively used it in the past. The library has a very solid API, so it's like one of those libs in Clojure where you add to the project and profit without maintenance burden.

OTOH, I used promesa in projects that heavily used promises everywhere (Node.js runtime instead of browser). Every code path had promises, so promesa paid off. In status-mobile we mostly have a few promises in re-frame effects (thankfully 😃). Therefore, our usages are very linear and simple, and I don't see promesa adding that much value.

I have also noticed promises code in status-mobile is quite convoluted and show some lack of knowledge of how promises work. Namespaces like react-native.cameraroll and react-native.permissions, among others show that to me because they confusingly mix callbacks with promises, probably an attempt to hide promises.

If we add promesa on top, I think it will be nice for some, but another layer on top of promises that some are not willing to learn.

In the end, I vote for adding it if you want in a separate PR, but better check with more devs.

(if migrated?
(callback)
(-> (get-user-password! key-uid identity)
(.then #(security/hash-masked-password %))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be just (.then security/hash-masked-password).

Also, it may be better to avoid calling callback if it's undefined or nil, so better wrap the call with a (when callback) check.

Copy link
Member Author

@clauxx clauxx Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went for a default value for the callback when destructuring instead, so we don't check twice for the callback

(-> (get-password-migration! key-uid identity)
(.then (fn [migrated?]
(if migrated?
(callback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If calling callback throws an exception, it will be swallowed and ignored since no other code is piping a .catch on the return value of the re-frame effect.

You can move the .catch outside the inner function so it also catches errors from calling callback, and then improving the error message if it has been migrated or not.

You may also use try/catch around (callback) if you want to assume that part of the code is synchronous. I prefer not to mix and assume that, but it should be fine because we don't use the return value of this re-frame effect.

@@ -154,7 +145,9 @@
biometric-enabled?
(assoc :keychain/save-password-and-auth-method
{:key-uid key-uid
:masked-password masked-password
:masked-password (if syncing?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this reads a bit funny is the variable actually "masked" if syncing? is true?
If not it seems the variable naming should be adjusted some what here 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's masked regardless of syncing?. The difference is that during syncing the password is already hashed (we don't have access to the plaintext password), so we need to hash it for the other cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems good as is so 👍

Copy link
Contributor

@ilmotta ilmotta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done @clauxx. I must say I haven't tested the PR build, but code LGTM.

:accessibility-label :enable-biometrics-button
:icon-left :i/face-id
:customization-color profile-color
:theme theme
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to pass theme as a prop here? it should be normally got by the context mechanism within the component 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we don't, removed all mentions of the theme in the file.

@@ -175,7 +190,7 @@
cofx
{:db (assoc-in db [:profile/login :password] password)}
(navigation/init-root :progress)
(login))))
(biometry-login))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is biometry appropriate here? should it not be biometrics-login ?
It kind of makes sense but never really heard of this domain referred to as "biometry"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using them interchangeably till now, but they seem to be different 😮

@@ -58,3 +59,8 @@
and does not contain an rtlo character, which might mean that the url is spoofed"
[text]
(not (re-matches rtlo-link-regex text)))

(def hash-masked-password
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add simple tests for these utility methods?
cc @ilmotta

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are asking the worst person :P I think pretty much all utility functions should have unit tests, simple or not. I refrain from commenting much in PRs about this, but I think this should be in our guidelines.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests for security/mask-data and security/hash-masked-password 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rockstar!

(keychain/save-credentials
(password-migration-key-name key-uid)
key-uid
;; NOTE: using the key-id as the password, but we don't really care about the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean key-id or key-uid here? looks like a typo

@@ -79,33 +78,88 @@
(if can-save?
(keychain/get-credentials
(str key-uid "-auth")
#(callback (if % (oops/oget % "password") auth-method-none)))
(fn [value]
(callback (if value (oops/oget value "password") auth-method-none))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should "password" be in a private def ? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So seems like oops considers const vars as "dynamic values" and throws warnings at compiled time, although it shouldn't for string constants. It would work with oops/oget+, but apparently it's slower .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the same, but still related, I prefer to pass keywords to oget because they only need to be allocated once. It's not important really, just a nano optimization, but feels more idiomatic to me 🤷🏼

@clauxx
Copy link
Member Author

clauxx commented Nov 22, 2023

@ilmotta @J-Son89 added tests, please have a look :)

@clauxx
Copy link
Member Author

clauxx commented Nov 22, 2023

@J-Son89 @ilmotta figured it's a nice opportunity to add my first schema in the last commit :)

[value]
(instance? MaskedData value))

(schema/=> hash-masked-password
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely to see you are using malli already ❤️

@clauxx, may I suggest some improvements for readability?

Function schema errors already say if the error is in the input or output schema, so whatever custom schema we define, the error message doesn't need to hardcode if the it's the argument or return value. This allows us to create reusable schemas either for input/output values.

I think by extracting to a custom var schema, we can make the function schema signature clearer, which is one of the main selling points of spec'ing functions. If we leave the custom schema right in the schema definition as you did, this makes the code less readable.

So here's a suggestion:

(def ?masked-password
  [:fn {:error/message "should be an instance of utils.security.core/MaskedData"}
   masked-data-instance?])

(schema/=> hash-masked-password
  [:=>
   [:cat ?masked-password]
   ?masked-password])

If schemas grow too much in the namespace, I think it's reasonable to create a namespace in the same directory named schema.cljs, but I like as you did since there's only one :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason to prefer this style is that custom schemas can easily grow. For example, especially when using :fn schemas, malli won't know how to generate random values conforming to the schema, so we would need a :schema/gen generator in the definition, thus making the inlined schema less readable still. So by extracting custom schemas to vars we promote more readable function schemas now and in the future as they evolve.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, it makes perfect sense! I like this way much better. Thanks!!

@pavloburykh pavloburykh force-pushed the 17928-sync-biometry-no-password branch from 3e9ce16 to adbc3e8 Compare November 24, 2023 13:20
@pavloburykh pavloburykh self-assigned this Nov 24, 2023
@pavloburykh pavloburykh force-pushed the 17928-sync-biometry-no-password branch from 10b0bd7 to 326db65 Compare November 29, 2023 19:59
@status-im-auto
Copy link
Member

62% of end-end tests have passed

Total executed tests: 48
Failed tests: 15
Expected to fail tests: 3
Passed tests: 30
IDs of failed tests: 702958,702786,703202,702841,702807,702808,702809,703629,703133,704614,702957,702782,702850,703297,703495 
IDs of expected to fail tests: 702731,702732,703503 

Failed tests (15)

Click to expand
  • Rerun failed tests

  • Class TestActivityCenterContactRequestMultipleDevicePR:

    1. test_activity_center_contact_request_decline, id: 702850

    Device 1: Tap on found: Button
    Device 1: Find `Text` by `xpath`: `//*[@content-desc="pending-contact-requests-count"]/android.widget.TextView`

    activity_center/test_activity_center.py:63: in test_activity_center_contact_request_decline
        if self.home_1.pending_contact_request_text.text != '1':
    ../views/base_element.py:406: in text
        text = self.find_element().text
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 1: Text by xpath: `//*[@content-desc="pending-contact-requests-count"]/android.widget.TextView` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    



    Device sessions

    Class TestCommunityMultipleDeviceMerged:

    1. test_community_unread_messages_badge, id: 702841

    # STEP: Check new messages badge is shown for community
    Device 1: Click until ChatMessageInput by accessibility id: chat-message-input will be presented

    critical/chats/test_public_chat_browsing.py:632: in test_community_unread_messages_badge
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     New message community badge is not shown
    



    Device sessions

    Class TestOneToOneChatMultipleSharedDevicesNewUi:

    1. test_1_1_chat_emoji_send_reply_and_open_link, id: 702782

    Device 2: Find OpenInStatusButton by xpath: //*[@text="Open in Status"]
    Device 2: Tap on found: OpenInStatusButton

    critical/chats/test_1_1_public_chats.py:174: in test_1_1_chat_emoji_send_reply_and_open_link
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     No reply received in 1-1 chat
    



    Device sessions

    Class TestCommunityOneDeviceMerged:

    1. test_restore_multiaccount_with_waku_backup_remove_switch, id: 703133

    Device 1: Find Button by accessibility id: show-profiles
    Device 1: Tap on found: Button

    critical/chats/test_public_chat_browsing.py:273: in test_restore_multiaccount_with_waku_backup_remove_switch
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     Incorrect contacts number restored: 0 instead of 2
    E    admin_open was not restored from waku-backup!!
    E    member_open was not restored from waku-backup!!
    E    admin_closed was not restored from waku-backup!!
    E    member_closed was not restored from waku-backup!!
    



    Device sessions

    Class TestActivityMultipleDevicePRTwo:

    1. test_activity_center_admin_notification_accept_swipe, id: 702958

    Test setup failed: activity_center/test_activity_center.py:397: in prepare_devices
        self.community_2.join_community()
    ../views/chat_view.py:433: in join_community
        self.community_status_joined.wait_for_visibility_of_element(60)
    ../views/base_element.py:139: in wait_for_visibility_of_element
        raise TimeoutException(
     Device 2: Text by accessibility id:`status-tag-positive` is not found on the screen after wait_for_visibility_of_element
    



    2. test_activity_center_mentions, id: 702957

    Device 2: Find Button by xpath: //*[@content-desc='password-input']/../following-sibling::*//*[@text='Join Community']
    Device 2: Tap on found: Button

    Test setup failed: activity_center/test_activity_center.py:397: in prepare_devices
        self.community_2.join_community()
    ../views/chat_view.py:433: in join_community
        self.community_status_joined.wait_for_visibility_of_element(60)
    ../views/base_element.py:139: in wait_for_visibility_of_element
        raise TimeoutException(
     Device 2: Text by accessibility id:`status-tag-positive` is not found on the screen after wait_for_visibility_of_element
    



    Device sessions

    Class TestGroupChatMultipleDeviceMergedNewUI:

    1. test_group_chat_reactions, id: 703202

    Test setup failed: ../views/home_view.py:382: in handle_contact_request
        chat_element.accept_contact_request()
    ../views/home_view.py:150: in accept_contact_request
        self.handle_cr("accept-contact-request")
    ../views/home_view.py:147: in handle_cr
        ).wait_for_rendering_ended_and_click()
    ../views/base_element.py:155: in wait_for_rendering_ended_and_click
        self.click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 3: Button by xpath: `//*[contains(@text, 'user admin')]/ancestor::*[@content-desc='activity']/*[@content-desc="accept-contact-request"]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    
    During handling of the above exception, another exception occurred:
    critical/chats/test_group_chat.py:54: in prepare_devices
        self.loop.run_until_complete(
    /usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
        return future.result()
    __init__.py:44: in run_in_parallel
        returns.append(await k)
    /usr/lib/python3.10/concurrent/futures/thread.py:58: in run
        result = self.fn(*self.args, **self.kwargs)
    ../views/home_view.py:392: in handle_contact_request
        self.driver.fail("No contact request received from %s" % username)
    base_test_case.py:179: in fail
        pytest.fail('Device %s: %s' % (self.number, text))
    E   Failed: Device 3: No contact request received from user admin
    



    2. test_group_chat_join_send_text_messages_push, id: 702807

    Device 3: Tap on found: Button
    Device 3: Attempt 0 is successful clicking close-activity-center

    Test setup failed: ../views/home_view.py:382: in handle_contact_request
        chat_element.accept_contact_request()
    ../views/home_view.py:150: in accept_contact_request
        self.handle_cr("accept-contact-request")
    ../views/home_view.py:147: in handle_cr
        ).wait_for_rendering_ended_and_click()
    ../views/base_element.py:155: in wait_for_rendering_ended_and_click
        self.click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 3: Button by xpath: `//*[contains(@text, 'user admin')]/ancestor::*[@content-desc='activity']/*[@content-desc="accept-contact-request"]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    
    During handling of the above exception, another exception occurred:
    critical/chats/test_group_chat.py:54: in prepare_devices
        self.loop.run_until_complete(
    /usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
        return future.result()
    __init__.py:44: in run_in_parallel
        returns.append(await k)
    /usr/lib/python3.10/concurrent/futures/thread.py:58: in run
        result = self.fn(*self.args, **self.kwargs)
    ../views/home_view.py:392: in handle_contact_request
        self.driver.fail("No contact request received from %s" % username)
    base_test_case.py:179: in fail
        pytest.fail('Device %s: %s' % (self.number, text))
    E   Failed: Device 3: No contact request received from user admin
    



    Device sessions

    3. test_group_chat_offline_pn, id: 702808

    Test setup failed: ../views/home_view.py:382: in handle_contact_request
        chat_element.accept_contact_request()
    ../views/home_view.py:150: in accept_contact_request
        self.handle_cr("accept-contact-request")
    ../views/home_view.py:147: in handle_cr
        ).wait_for_rendering_ended_and_click()
    ../views/base_element.py:155: in wait_for_rendering_ended_and_click
        self.click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 3: Button by xpath: `//*[contains(@text, 'user admin')]/ancestor::*[@content-desc='activity']/*[@content-desc="accept-contact-request"]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    
    During handling of the above exception, another exception occurred:
    critical/chats/test_group_chat.py:54: in prepare_devices
        self.loop.run_until_complete(
    /usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
        return future.result()
    __init__.py:44: in run_in_parallel
        returns.append(await k)
    /usr/lib/python3.10/concurrent/futures/thread.py:58: in run
        result = self.fn(*self.args, **self.kwargs)
    ../views/home_view.py:392: in handle_contact_request
        self.driver.fail("No contact request received from %s" % username)
    base_test_case.py:179: in fail
        pytest.fail('Device %s: %s' % (self.number, text))
    E   Failed: Device 3: No contact request received from user admin 
    

    [[Data delivery issue]]

    4. test_group_chat_send_image_save_and_share, id: 703297

    Test setup failed: ../views/home_view.py:382: in handle_contact_request
        chat_element.accept_contact_request()
    ../views/home_view.py:150: in accept_contact_request
        self.handle_cr("accept-contact-request")
    ../views/home_view.py:147: in handle_cr
        ).wait_for_rendering_ended_and_click()
    ../views/base_element.py:155: in wait_for_rendering_ended_and_click
        self.click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 3: Button by xpath: `//*[contains(@text, 'user admin')]/ancestor::*[@content-desc='activity']/*[@content-desc="accept-contact-request"]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    
    During handling of the above exception, another exception occurred:
    critical/chats/test_group_chat.py:54: in prepare_devices
        self.loop.run_until_complete(
    /usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
        return future.result()
    __init__.py:44: in run_in_parallel
        returns.append(await k)
    /usr/lib/python3.10/concurrent/futures/thread.py:58: in run
        result = self.fn(*self.args, **self.kwargs)
    ../views/home_view.py:392: in handle_contact_request
        self.driver.fail("No contact request received from %s" % username)
    base_test_case.py:179: in fail
        pytest.fail('Device %s: %s' % (self.number, text))
    E   Failed: Device 3: No contact request received from user admin
    



    5. test_group_chat_mute_chat, id: 703495

    Test setup failed: ../views/home_view.py:382: in handle_contact_request
        chat_element.accept_contact_request()
    ../views/home_view.py:150: in accept_contact_request
        self.handle_cr("accept-contact-request")
    ../views/home_view.py:147: in handle_cr
        ).wait_for_rendering_ended_and_click()
    ../views/base_element.py:155: in wait_for_rendering_ended_and_click
        self.click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 3: Button by xpath: `//*[contains(@text, 'user admin')]/ancestor::*[@content-desc='activity']/*[@content-desc="accept-contact-request"]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    
    During handling of the above exception, another exception occurred:
    critical/chats/test_group_chat.py:54: in prepare_devices
        self.loop.run_until_complete(
    /usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
        return future.result()
    __init__.py:44: in run_in_parallel
        returns.append(await k)
    /usr/lib/python3.10/concurrent/futures/thread.py:58: in run
        result = self.fn(*self.args, **self.kwargs)
    ../views/home_view.py:392: in handle_contact_request
        self.driver.fail("No contact request received from %s" % username)
    base_test_case.py:179: in fail
        pytest.fail('Device %s: %s' % (self.number, text))
    E   Failed: Device 3: No contact request received from user admin
    



    Class TestCommunityMultipleDeviceMergedTwo:

    1. test_community_mentions_push_notification, id: 702786

    Device 2: Getting PN by 'user_2'
    Device 2: Looking for a message by text: user_2

    critical/chats/test_public_chat_browsing.py:901: in test_community_mentions_push_notification
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     Edited message is not shown correctly for the (receiver) admin
    



    Device sessions

    2. test_community_markdown_support, id: 702809

    Device 1: Looking for a message by text: quote reply (one row)
    Device 2: Looking for a message by text: quote reply (one row)

    critical/chats/test_public_chat_browsing.py:953: in test_community_markdown_support
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     bold text in underscores is not displayed with markdown in community channel for the recipient (device 1) 
    E   
    E    italic text in asteric is not displayed with markdown in community channel for the recipient (device 1) 
    E   
    E    italic text in underscore is not displayed with markdown in community channel for the recipient (device 1) 
    E   
    E    inline code is not displayed with markdown in community channel for the recipient (device 1) 
    E   
    E    code blocks is not displayed with markdown in community channel for the recipient (device 1)
    



    Device sessions

    3. test_community_join_when_node_owner_offline, id: 703629

    Device 2: Looking for community: 'open community'
    Device 2: Click until Text by accessibility id: community-description-text will be presented

    critical/chats/test_public_chat_browsing.py:1112: in test_community_join_when_node_owner_offline
        self.errors.verify_no_errors()
    base_test_case.py:191: in verify_no_errors
        pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
     Text "You joined “closed community”" in shown toast element doesn't match expected "You joined “open community”"
    



    Device sessions

    Class TestDeepLinksOneDevice:

    1. test_links_open_universal_links_from_other_apps, id: 704614

    # STEP: Opening a community URL from google search bar when user is logged out
    Device 1: Find Button by xpath: //*[contains(@resource-id,'search_container_all_apps')]

    critical/test_deep_and_universal_links.py:107: in test_links_open_universal_links_from_other_apps
        self.home.open_link_from_google_search_app(community_url)
    ../views/base_view.py:846: in open_link_from_google_search_app
        Button(self.driver, xpath="//*[contains(@resource-id,'search_container_all_apps')]").click()
    ../views/base_element.py:90: in click
        self.find_element().click()
    ../views/base_element.py:79: in find_element
        raise NoSuchElementException(
     Device 1: Button by xpath: `//*[contains(@resource-id,'search_container_all_apps')]` is not found on the screen; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    



    Device sessions

    Expected to fail tests (3)

    Click to expand

    Class TestOneToOneChatMultipleSharedDevicesNewUi:

    1. test_1_1_chat_pin_messages, id: 702731

    Test is not run, e2e blocker  
    

    [[reason: [NOTRUN] Pin feature is in development]]

    Class TestGroupChatMultipleDeviceMergedNewUI:

    1. test_group_chat_pin_messages, id: 702732

    Test is not run, e2e blocker  
    

    [[reason: [NOTRUN] Pin feature is in development]]

    Class TestCommunityOneDeviceMerged:

    1. test_community_discovery, id: 703503

    Test is not run, e2e blocker  
    

    [[reason: [NOTRUN] Curated communities not loading, https://github.com//issues/17852]]

    Passed tests (30)

    Click to expand

    Class TestActivityMultipleDevicePR:

    1. test_activity_center_reply_read_unread_delete_filter_swipe, id: 702947
    Device sessions

    2. test_navigation_jump_to, id: 702936
    Device sessions

    Class TestActivityCenterContactRequestMultipleDevicePR:

    1. test_activity_center_contact_request_accept_swipe_mark_all_as_read, id: 702851
    Device sessions

    2. test_add_contact_field_validation, id: 702777
    Device sessions

    Class TestCommunityMultipleDeviceMerged:

    1. test_community_emoji_send_copy_paste_reply, id: 702840
    Device sessions

    2. test_community_contact_block_unblock_offline, id: 702894
    Device sessions

    3. test_community_mark_all_messages_as_read, id: 703086
    Device sessions

    4. test_community_links_with_previews_github_youtube_twitter_gif_send_enable, id: 702844
    Device sessions

    5. test_community_message_delete, id: 702839
    Device sessions

    6. test_community_message_send_check_timestamps_sender_username, id: 702838
    Device sessions

    7. test_community_one_image_send_reply, id: 702859
    Device sessions

    8. test_community_message_edit, id: 702843
    Device sessions

    9. test_community_several_images_send_reply, id: 703194
    Device sessions

    Class TestOneToOneChatMultipleSharedDevicesNewUi:

    1. test_1_1_chat_edit_message, id: 702855
    Device sessions

    2. test_1_1_chat_message_reaction, id: 702730
    Device sessions

    3. test_1_1_chat_non_latin_messages_stack_update_profile_photo, id: 702745
    Device sessions

    4. test_1_1_chat_text_message_delete_push_disappear, id: 702733
    Device sessions

    5. test_1_1_chat_push_emoji, id: 702813
    Device sessions

    6. test_1_1_chat_send_image_save_and_share, id: 703391
    Device sessions

    Class TestCommunityOneDeviceMerged:

    1. test_community_copy_and_paste_message_in_chat_input, id: 702742
    Device sessions

    2. test_community_navigate_to_channel_when_relaunch, id: 702846
    Device sessions

    3. test_community_undo_delete_message, id: 702869
    Device sessions

    4. test_community_mute_community_and_channel, id: 703382
    Device sessions

    Class TestCommunityMultipleDeviceMergedTwo:

    1. test_community_leave, id: 702845
    Device sessions

    2. test_community_hashtag_links_to_community_channels, id: 702948
    Device sessions

    Class TestDeepLinksOneDevice:

    1. test_links_deep_links, id: 702775
    Device sessions

    2. test_links_open_universal_links_from_chat, id: 704613
    Device sessions

    Class TestOneToOneChatMultipleSharedDevicesNewUiTwo:

    1. test_1_1_chat_mute_chat, id: 703496
    Device sessions

    2. test_1_1_chat_is_shown_message_sent_delivered_from_offline, id: 702783
    Device sessions

    3. test_1_1_chat_delete_via_long_press_relogin, id: 702784
    Device sessions

    @pavloburykh
    Copy link
    Contributor

    @clauxx @cammellos got one question: could current changes somehow affect data delivery reliability? For some reason lot of e2e are failing because CR has not been delivered or community status remains Pending instead of switching to Joined and so on.

    This is suspicious, cause I don't observe such failures in other PRs that do not contain any go changes. @clauxx let's hold on with merging it, I want to make sure that described problems 100% not related to the PR changes. I will compare e2e results tomorrow with nightly/other prs.

    So far I cannot reproduce manually those issues caught by e2e. But I have made lot of re-runs and tests keep failing.

    @pavloburykh
    Copy link
    Contributor

    @clauxx today e2e are failing in other PRs too. So these failures are not PR related. We can merge it.

    @clauxx clauxx merged commit ed270b2 into develop Nov 30, 2023
    6 checks passed
    @clauxx clauxx deleted the 17928-sync-biometry-no-password branch November 30, 2023 11:40
    yevh-berdnyk pushed a commit that referenced this pull request Dec 8, 2023
    * feat: added migration for the keychain hashed password
    
    * feat: added sync biometry without password entry
    
    * fix: biometry typo from develop
    
    * ref: moved migration side-effects outside the event
    
    * ref: some renaming for keychain migration
    
    * ref: addressed @cammellos' review comments
    
    * ref: removed unnecessary anon fn
    
    * fix: addressed @ilmotta's review comments
    
    * ref: removed theme from enable-biometrics
    
    * ref: addressed J-Son89's review comments
    
    * test: added tests for mask-data and hash-masked-password
    
    * test: added schema to hash-masked-password and fixed test
    
    * fix: forgot the threading
    
    * ref: improved the masked data schema
    
    * fix: no biometry error when canceled by user
    
    * fix: biometry error wasn't propagated during login
    
    * fix: alert dismiss button not passed properly
    
    * fix: show biometrics NOT_ENROLLED error only once
    
    * lint: removed unused require
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    Archived in project
    Archived in project
    Development

    Successfully merging this pull request may close these issues.

    Do not ask for password when enabling biometrics during sync
    6 participants