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

Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore #10649

Merged
merged 27 commits into from
Jul 1, 2024

Conversation

pedr
Copy link
Collaborator

@pedr pedr commented Jun 21, 2024

Description

The goal of this PR is to improve the user experience when using Joplin Cloud as synchronisation target.

Currently, when the Joplin Cloud credentials expire (because the user enabled MFA or deleted the application record from the Joplin Cloud website) and tries to synchronise, there is only a small error message above the synchronise button.

To make it clear what is happening when the credentials aren't valid anymore, we are redirecting the user to Joplin Cloud Login to guide to user to finish the login process to be able to continue connected.

This behaviour is similar to what we have for fresh installs: if Joplin Cloud is selected as the synchronisation target but the login process isn't finished, we redirect the user to Joplin Cloud Login when he presses 'Synchronisation' on the sidebar.

Shortcommings

While I think this implementation is an improvement by making the process easier for the user, there is the issue that we won't be able to show the error messages inside the interface. In the previous implementation checkConfig was called from inside the React component which allowed the error message to be displayed where it was called.

This might be confusing if there is an error happening and it is only possible to see inside the logs.

This is not really a problem anymore since we have a notification banner informing the user the state of the connection.

Clips

First login (application without any state):

first_login.mp4

User was logged in, but credentials turned invalid (mfa was enabled or application was deleted on website):

credential_invalid.mp4

When Joplin Cloud is offline:

joplin_cloud_offline.mp4

Testing

Several cases need to be tested:

Fresh install

  • Press 'Synchronise' button
  • Select Joplin Cloud as sync target
  • Should be redirected to Joplin Cloud Login
  • Connect to Joplin Cloud
  • Go back
  • Pressing 'Synchronise' button should start synchronise process again

Invalid credentials

  • Synchronise to Joplin Cloud
  • Delete application record that allow the synchronisation in Joplin Cloud website
  • Press 'Synchronise' button
  • Should be redirect to Joplin Cloud Login screen since credentials are invalid now

Valid credentials on Config screen

  • Synchronise to Joplin Cloud
  • On the Synchronisation screen in the Configurations press 'Check synchronisation configuration'
  • Should show a successful message below the button

Invalid credentials on Config screen

  • Synchronise to Joplin Cloud
  • Delete application record that allow the synchronisation in Joplin Cloud website
  • On the Synchronisation screen in the Configurations press 'Check synchronisation configuration'
  • Should redirect user to Joplin Cloud Login screen

@laurent22
Copy link
Owner

Failing test on CI:

➤ YN0000: [@joplin/lib]: FAIL components/shared/config/shouldShowMissingPasswordWarning.test.js
➤ YN0000: [@joplin/lib]:   ● shouldShowMissingPasswordWarning › should return true when sync target requires a password and the password is missing
➤ YN0000: [@joplin/lib]: 
➤ YN0000: [@joplin/lib]:     expect(received).toBe(expected) // Object.is equality
➤ YN0000: [@joplin/lib]: 
➤ YN0000: [@joplin/lib]:     Expected: true
➤ YN0000: [@joplin/lib]:     Received: false
➤ YN0000: [@joplin/lib]: 
➤ YN0000: [@joplin/lib]:       20 | 			const expected = targetToRequiresPassword[targetName];
➤ YN0000: [@joplin/lib]:       21 |
➤ YN0000: [@joplin/lib]:     > 22 | 			expect(shouldShowMissingPasswordWarning(targetId, {})).toBe(expected);
➤ YN0000: [@joplin/lib]:          | 			                                                       ^
➤ YN0000: [@joplin/lib]:       23 |
➤ YN0000: [@joplin/lib]:       24 | 			// Should also consider an empty string to be missing
➤ YN0000: [@joplin/lib]:       25 | 			const settings = {
➤ YN0000: [@joplin/lib]: 
➤ YN0000: [@joplin/lib]:       at Object.toBe (components/shared/config/shouldShowMissingPasswordWarning.test.ts:22:59)

};
}, [intervalIdentifier]);
}, [intervalIdentifier, state, props.dispatch]);
Copy link
Owner

Choose a reason for hiding this comment

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

We should only pass what's needed for the effect, so only state.active here

Copy link
Owner

Choose a reason for hiding this comment

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

Also I don't really understand why this effect does and why it's modifying global state?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My initial idea was that the message in the sidebar might confuse the user if he just enters Joplin Cloud Login screen and leaves without taking any action, it might say that the synchronisation was complete, for example, while the credentials are invalid at the point he is reading it.

I think the notification alert addresses this by making it clear what is happening, I don't think this event is necessary anymore, I will remove it.

@pedr pedr marked this pull request as ready for review June 25, 2024 18:18
@pedr pedr changed the title Desktop, Mobile: Open Joplin Cloud login screen when credentials fail Desktop, Mobile: Fixes #10645: Open Joplin Cloud login screen when credentials fail Jun 26, 2024
@pedr
Copy link
Collaborator Author

pedr commented Jun 27, 2024

  • Remove messages about Joplin Cloud being offline

@pedr
Copy link
Collaborator Author

pedr commented Jun 27, 2024

@laurent22 I removed the message about Joplin Cloud being offline.

I also changed the logic about the error catch inside isAuthenticated, I'm assuming any error.code that isn't > 500 should be a validation error (wrong credentials, or application credential doesn't exist yet) and anything else we assume it is an unknown error (like you said, it could be our server, but could also be user connection, proxy, DNS blocker, etc)

if (error.code < 500) {
Setting.setValue(`sync.${SyncTargetJoplinCloud.id()}.isAuthenticated`, false);
return false;
}
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure we should have this logic here because when is it actually called? Also it's called "isAuthenticated" but it's clearly doing a lot more than this now, including saving settings.

Is it possible to add the code to lib/Synchronizer.ts instead, with the same logic as "MustUpgradeApp" error? If we catch an error, and if it's an auth error, and it's Joplin Cloud, then dispatch an action telling the user they must authenticate. That way we deal with the problem exactly where it happens.

Copy link
Collaborator Author

@pedr pedr Jun 28, 2024

Choose a reason for hiding this comment

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

I'm going to still test your suggestion, and I see how what I'm doing is really not necessary at this point (I'm removing the Setting.setValue and checkConfig), but I think we still need to have some error catch logic here.

It is necessary because when we call api.sessionId() and the user has invalid credentials it will throw an error (probably 403 or 404, depending on why the credentials are invalid).

The credentials can be invalid if the user:

  • Has enabled MFA and hasn't made a new login in the client yet.
  • Has deleted the application record from the Joplin Cloud page.

Edit: just to be clear, this happens when the user presses 'Synchronise', not when a schedule sync happens. I think your suggestion makes sense, I will try to implement it ASAP.

Copy link
Owner

Choose a reason for hiding this comment

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

The credentials can be invalid if the user:

For now let's narrow it down to a specific case: we want to display a message when the session ID is cleared (when MFA is enabled). Whatever is the error code then we should display a message. Anything else we don't handle it at all.

@pedr pedr marked this pull request as draft June 28, 2024 14:11
@pedr pedr marked this pull request as ready for review June 28, 2024 16:52
@pedr
Copy link
Collaborator Author

pedr commented Jun 28, 2024

I had to call 'MUST_AUTHENTICATE' in three places:

Inside the lib/register, after IsAutheticated is called because it is where the schedule sync checks if should start or not. If we don't have a sessionId (because it is a client that hasn't connected yet) or if we try to get a session but get an error of 'Invalid authentication code' (aka MFA was enabled) we set MUST_AUTHENTICATE to true, else is set to false

And inside the Synchronizer, because we might have a sesionId (so MUST_AUTHENTICATE was set to true) but when the Synchronize process start when we try to fetchSyncInfo() function we get an error because the session is not valid anymore (because MFA was enabled).

@pedr
Copy link
Collaborator Author

pedr commented Jun 28, 2024

I hope this solution is aligned with what you expected, else feel free to ask for changes.

Sorry for taking so long on this, but this kind of interaction between the client and server, is authenticated or not, is mfa enabled or not, etc requires a lot of manual testing and I like to be on the safe side and test it two times if I'm not sure.

@pedr pedr changed the title Desktop, Mobile: Fixes #10645: Open Joplin Cloud login screen when credentials fail Desktop, Mobile: Fixes #10645: Show notification in case Joplin Cloud credential is not valid anymore Jun 28, 2024
@@ -6,7 +6,7 @@ import NavService from '@joplin/lib/services/NavService';

interface Props {
themeId: number;
targetScreen: string;
targetScreen?: string;
Copy link
Owner

Choose a reason for hiding this comment

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

Is that needed? It looks like targetScreen is not used in your code?

this.dispatch_ = dispatch;
}

private sendDispatch(event: AnyAction) {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
private sendDispatch(event: AnyAction) {
private dispatch(event: AnyAction) {

@@ -524,6 +524,9 @@ export default class Synchronizer {
// await uploadSyncInfo(this.api(), remoteInfo);
}
} catch (error) {
if (error.message === 'Invalid authentication code') {
Copy link
Owner

Choose a reason for hiding this comment

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

Isn't Joplin Cloud setting an http status code, and can we use this instead? We shouldn't check error message as they can change in the future.

this.dispatch_ = dispatch;
}

private sendDispatch(event: AnyAction) {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
private sendDispatch(event: AnyAction) {
private sendDispatch(action: AnyAction) {

}

private sendDispatch(event: AnyAction) {
if (!this.dispatch_) throw new Error('Dispatch not set!');
Copy link
Owner

Choose a reason for hiding this comment

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

Please make it optional, defaults to (action) => {}. I'm not keen that we have to add this dispatch thing to the registry now, but if there's no other way let's do it, but it should be optional.

@@ -684,6 +692,12 @@ class MainScreenComponent extends React.Component<Props, State> {
);
} else if (this.props.mustUpgradeAppMessage) {
msg = this.renderNotificationMessage(this.props.mustUpgradeAppMessage);
} else if (this.props.showInvalidJoplinCloudCredential) {
msg = this.renderNotificationMessage(
_('Your Joplin Cloud credentials are invalid., please re-authenticate.'),
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
_('Your Joplin Cloud credentials are invalid., please re-authenticate.'),
_('Your Joplin Cloud credentials are invalid, please login.'),

And please also update the other string

} else if (this.props.showInvalidJoplinCloudCredential) {
msg = this.renderNotificationMessage(
_('Your Joplin Cloud credentials are invalid., please re-authenticate.'),
_('Go to Joplin Cloud Login'),
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
_('Go to Joplin Cloud Login'),
_('Login to Joplin Cloud'),

Comment on lines 238 to 241
// if (Setting.value('env') === 'dev') {
// this.logger().info('Recurrent sync operation DISABLED!!!');
// return;
// }
Copy link
Owner

Choose a reason for hiding this comment

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

that should not be in the pr

Comment on lines +527 to +529
if (error.code === 403) {
this.dispatch({ type: 'MUST_AUTHENTICATE', value: true });
}
Copy link
Owner

Choose a reason for hiding this comment

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

Ok but other sync targets can set the 403 status code so it means we're going to show a message about Joplin Cloud when someone can't login to an unrelated service. I'm going to try to fix this issue

Copy link
Collaborator Author

@pedr pedr Jun 29, 2024

Choose a reason for hiding this comment

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

I'm checking the sync target in the place where it the notification is shown to avoid this problem

https://github.com/laurent22/joplin/pull/10649/files#diff-a400aeaf7c8882c4911f24f17d6e53c17f706ec22055ea21906882bb66590d7bR983

@laurent22 laurent22 merged commit a074532 into laurent22:dev Jul 1, 2024
8 checks passed
laurent22 pushed a commit that referenced this pull request Jul 1, 2024
@pedr pedr deleted the improve-joplin-cloud-connection-ux branch July 9, 2024 18:55
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.

2 participants