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

Feat/call check for revocation #1372

Merged
merged 6 commits into from
Jul 27, 2023
Merged

Feat/call check for revocation #1372

merged 6 commits into from
Jul 27, 2023

Conversation

Crash--
Copy link
Contributor

@Crash-- Crash-- commented Jul 20, 2023

When we got an error for OAuth token, we check if it's something that can be used for revokation. If yes, then we make a call to know if the client is revocked or not.

BREAKING CHANGE: checkForRevocation() is only callable when dealing
with an OAuthClient.

It should not break your app, since if you called this method on a
stackClient before, it should have crashed your app.
@acezard acezard self-assigned this Jul 24, 2023
@Crash--
Copy link
Contributor Author

Crash-- commented Jul 24, 2023

@acezard I would have add a test for the scenario:

  • fetch throw an error
  • we call refreshToken
  • refreshToken throw an error
  • we call checkForRevocation

In some cases the promise rejection is not handled at all
by refreshToken,
since it was not really clear what was happening it seemed
safer to add an early check
@acezard acezard changed the title [WIP] Feat/call check for revocation Feat/call check for revocation Jul 25, 2023
@acezard
Copy link
Contributor

acezard commented Jul 25, 2023

@acezard I would have add a test for the scenario:

  • fetch throw an error
  • we call refreshToken
  • refreshToken throw an error
  • we call checkForRevocation

@Crash-- test added, from my side everything seems good but I'm not sure I'm receiving the expected messages on revocation (with a registered client). But it still works with any error on refreshToken, we'll always do an early check

)
// Have to catch early to check for revocation because it seems the error is not caught by the catch below in some cases (e.g. when the client is not registered, even if it should be)
.catch(async e => {
if (await this.checkForRevocation()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Y a un truc que je comprends pas :

  • déjà ce catch pourquoi il doit être de cette forme plutôt que le catch plus bas ? 🤔
  • le checkForRevocation fait un fetchInformation, qui lui même throw déjà une erreur throw new NotRegisteredException(). Du coup pourquoi avoir besoin de lever cette erreur plutôt que celle déjà levée?

Copy link
Contributor

Choose a reason for hiding this comment

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

je n'ai pas réussi à trouver la source du problème mais sans le catch chained, certaines erreurs (dont celle qui m'intéressait pour être revoked) n'étaient tout simplement jamais catch, comme si elles étaient catch ailleurs. En chainant le catch à la promise, le problème ne s'est jamais présenté à nouveau

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK ça c'est le premier point, et le second ?

Copy link
Contributor

Choose a reason for hiding this comment

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

OK ça c'est le premier point, et le second ?

le second, c'est que justement si je me révoque depuis settings web, la première erreur que je reçois est un "client not found", alors qu'effectivement si je comprends bien je devrais avoir un Invalid token. Mais ce n'est pas ce que m'envoie la stack en master last commit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pourquoi tu penses que tu devrais recevoir un invalid token? On l'a déjà reçu en premier lieu, ensuite on fait un fetchInformations() qui va vérifier si justement le client existe. De là ça va throw puis passer ici https://github.com/cozy/cozy-client/pull/1372/files#diff-7818881da36e23a808dace9ce34c92ea922a6151ef1a65b66c10667bcf3660bdR678 et donc appeler le onRevocationChange. Mais l'erreur que tu vas recevoir c'est effectivement client not found.

C'est pas logique ?

Copy link
Member

Choose a reason for hiding this comment

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

Non le corps de la réponse contenait déjà the client must be registered. Mais cozy-client se base sur la valeur du header www-authenticate pour déterminer le message de la FetchError normalement (voir https://github.com/cozy/cozy-client/blob/master/packages/cozy-stack-client/src/errors.js#L56).
En tous cas c'est ce qu'il se passe dans mes essais.

Copy link
Contributor

Choose a reason for hiding this comment

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

anc@desktop:~/cozy/cozy-client$ curl -v -X POST 'http://dev.192-168-1-65.nip.io:8080/auth/access_token' -H 'accept: application/json' -H 'Accept-Encoding: gzip' -H 'authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhY2Nlc3MiLCJpYXQiOjE2OTAyOTAwNDUsImlzcyI6ImRldi4xOTItMTY4LTEtNjUubmlwLmlvOjgwODAiLCJzdWIiOiJlZDczY2E0Njc0MmZhNjliMWM2ZDAzZDI4MDUxY2U4MyIsInNjb3BlIjoiKiJ9.0s-LzKsemSlJkPllcNyw0QfZ_ve2ux9xKcsQaH2S8aQv8QFP0InJTf1GRmGZok7iUdBUJSEgeyPnoXNLIrsPRw' -H 'Connection: Keep-Alive' -H 'Content-Length: 419' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cookie: _csrf=JmInvLRunjQDXYKLSFWSejcXXoBKEmlY; cozysessid=AAAAAGS_xAJlZDczY2E0Njc0MmZhNjliMWM2ZDAzZDI4MDUxNGM1YQ-b1cz6d-rpHyWKDLvo0iP15IKfE5UI7G1D7nhTC6qO' -H 'Host: dev.192-168-1-65.nip.io:8080' -H 'User-Agent: io.cozy.flagship.mobile-1.1.10' -d 'grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZWZyZXNoIiwiaWF0IjoxNjkwMjkwMDQ1LCJpc3MiOiJkZXYuMTkyLTE2OC0xLTY1Lm5pcC5pbzo4MDgwIiwic3ViIjoiZWQ3M2NhNDY3NDJmYTY5YjFjNmQwM2QyODA1MWNlODMiLCJzY29wZSI6IioifQ.U7dTHg385sJyTiSBVICWHWNEJX5M0mWFgC5a_ye8ZrYWSEb_SIaH5rBzgndrTPADYIc-dXK40pJokamTj7VUoA&client_id=ed73ca46742fa69b1c6d03d28051ce83&client_secret=83ynXwyla94mWHtYiSzP-sFia5FPZ2nS'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 192.168.1.65:8080...
* Connected to dev.192-168-1-65.nip.io (192.168.1.65) port 8080 (#0)
> POST /auth/access_token HTTP/1.1
> Host: dev.192-168-1-65.nip.io:8080
> accept: application/json
> Accept-Encoding: gzip
> authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhY2Nlc3MiLCJpYXQiOjE2OTAyOTAwNDUsImlzcyI6ImRldi4xOTItMTY4LTEtNjUubmlwLmlvOjgwODAiLCJzdWIiOiJlZDczY2E0Njc0MmZhNjliMWM2ZDAzZDI4MDUxY2U4MyIsInNjb3BlIjoiKiJ9.0s-LzKsemSlJkPllcNyw0QfZ_ve2ux9xKcsQaH2S8aQv8QFP0InJTf1GRmGZok7iUdBUJSEgeyPnoXNLIrsPRw
> Connection: Keep-Alive
> Content-Length: 419
> Content-Type: application/x-www-form-urlencoded
> Cookie: _csrf=JmInvLRunjQDXYKLSFWSejcXXoBKEmlY; cozysessid=AAAAAGS_xAJlZDczY2E0Njc0MmZhNjliMWM2ZDAzZDI4MDUxNGM1YQ-b1cz6d-rpHyWKDLvo0iP15IKfE5UI7G1D7nhTC6qO
> User-Agent: io.cozy.flagship.mobile-1.1.10
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=UTF-8
< Date: Tue, 25 Jul 2023 13:09:16 GMT
< Content-Length: 42
< 
{"error":"the client must be registered"}
* Connection #0 to host dev.192-168-1-65.nip.io left intact

curl de mon POST access_token pour ref. Je n'ai pas l'impression qu'il y ait le header www-authenticate

Copy link
Contributor

Choose a reason for hiding this comment

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

par contre je l'ai bien à la requête qui précède (donc celle qui trigger le refreshToken)
image

Copy link
Member

Choose a reason for hiding this comment

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

Pour info, dans mes tests, Desktop faisait une requête sur /settings/disk-usage.
C'est cette requête qui recevait une erreur Expired Token déclenchant un refresh.

Copy link
Member

@taratatach taratatach Jul 25, 2023

Choose a reason for hiding this comment

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

Je viens de tester à nouveau et effectivement cozy-client ne voit cette erreur ni comme un Expired Token ni comme un Invalid Token parce qu'elle utilise le corps de la réponse comme reason.

@Ldoppea
Copy link
Member

Ldoppea commented Jul 25, 2023

@acezard I'm wondering if it would be easier to read the PR with integration tests that mock only the fetch returns

I did that in another PR and it has the advantage of making easier to vizualize the different scenario and what are the cozy-stack answers:
https://github.com/cozy/cozy-client/pull/1352/files#diff-a5c3691d6cd920e73de39e425c774e7589a6fb926621b2b9718cb7c2b9ebccb9R426-R450

@acezard
Copy link
Contributor

acezard commented Jul 25, 2023

@acezard I'm wondering if it would be easier to read the PR with integration tests that mock only the fetch returns

I did that in another PR and it has the advantage of making easier to vizualize the different scenario and what are the cozy-stack answers: https://github.com/cozy/cozy-client/pull/1352/files#diff-a5c3691d6cd920e73de39e425c774e7589a6fb926621b2b9718cb7c2b9ebccb9R426-R450

I agree on principle, but from my tests in runtime, the stack didn't send what I expected. Thus, I preferred to assume the end result of the calls, not the stack answers. But I agree that I don't like mocking internal methods in a test

@acezard acezard force-pushed the feat/CallCheckForRevocation branch from f515e75 to 5e198d2 Compare July 26, 2023 07:00
})
return newToken
} catch (e) {
if (errors.UNREGISTERED_CLIENT.test(e.message)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why not using this.isRevocationError ?

Copy link
Contributor

@acezard acezard Jul 26, 2023

Choose a reason for hiding this comment

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

Yes but then we need to update it because currently it only tests for const CLIENT_NOT_FOUND = /Client not found/. I'm on it

  • Updated

this.setToken(newToken)
throw new Error(
'Client has been revoked or does not exist. Please authenticate again.'
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if we should change the error message. Shouldn't we throw the real error? Or a fetch error?

Copy link
Contributor

@acezard acezard Jul 26, 2023

Choose a reason for hiding this comment

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

Well yes I guess I wanted to imitate the checkForRevocation conditional

        const revoked = await this.checkForRevocation()

        if (revoked) {
          throw new Error('Client has been revoked. Please authenticate again.')
        }

I'm not sure if it's still a fetch error at this point, it's more a reaction to a fetch error. But we could indeed at least keep the error message and add more info saying that error message probably happens because client was either revoked or doesn't exist (both can be true)

Copy link
Contributor

@acezard acezard Jul 26, 2023

Choose a reason for hiding this comment

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

  • Updated


return newToken
if (revoked) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

same here, I think that checkForRevocation() should throw the real error.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, then the initial fetcherror? as I mentioned earlier, I think we should add some more context info maybe. Like adding we think the client is revoked (I mean it's 100% sure it doesn't exist, but it's either revoked or non-existing)

Copy link
Contributor

@acezard acezard Jul 26, 2023

Choose a reason for hiding this comment

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

  • Updated

*
* @async
* @returns {Promise<boolean>} A Promise that resolves to `false` if client is still valid, or `true` if it has been revoked.
* @throws {Error} If an error occurs while fetching the client information.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With this implementation, this is not true, but we should throw https://github.com/cozy/cozy-client/pull/1372/files#r1274467375

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, it's fine by me I think both throwing a generic error or the initial fetcherror is fine as long as some context info is added during handling of the initial error

Copy link
Contributor

@acezard acezard Jul 26, 2023

Choose a reason for hiding this comment

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

  • Updated

@acezard acezard force-pushed the feat/CallCheckForRevocation branch 4 times, most recently from dc3bc1a to ecae5a2 Compare July 26, 2023 07:39
*
* @async
* @returns {Promise<boolean>} A Promise that resolves to `false` if client is still valid, or `true` if it has been revoked.
* @throws {FetchError} If an error occurs while fetching the client information.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method doesn't throw

Copy link
Contributor

Choose a reason for hiding this comment

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

@Crash-- updated

@acezard acezard force-pushed the feat/CallCheckForRevocation branch 2 times, most recently from 6a56c15 to 6d9303a Compare July 27, 2023 07:37
@acezard acezard self-requested a review July 27, 2023 07:37
@acezard acezard force-pushed the feat/CallCheckForRevocation branch from 6d9303a to 3899557 Compare July 27, 2023 07:41
@Crash-- Crash-- merged commit 0e9b96b into master Jul 27, 2023
4 checks passed
@Crash-- Crash-- deleted the feat/CallCheckForRevocation branch July 27, 2023 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants