-
Notifications
You must be signed in to change notification settings - Fork 42
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
When token invalid user must log back in #750
Conversation
4a0f7d2
to
47c04fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While clicking on logout (just after logging in, and I can see the source names in the source list) causes a crash.
Traceback (most recent call last):
File "/home/kdas/code/securedrop-client/securedrop_client/logic.py", line 288, in on_queue_paused
self.logout()
File "/home/kdas/code/securedrop-client/securedrop_client/logic.py", line 495, in logout
self.call_api(self.api.logout,
AttributeError: 'NoneType' object has no attribute 'logout'
@kushaldas what is the STR? There shouldn't be any log out buttons showing when the token is invalid and we open the login window |
also, this doesn't sound related to this PR |
|
This isn't quite right. The bang-message on the Login window is to communicate an error from that screen, in response to something the user did—not an error that lands the user on the Login page. Also, this window was never designed to scale with all the needs of an in-app login (such as being logged-out do to session inactivity, etc). At some point, the in-app login just needs to get implemented. To get the messaging right so this can go out for Beta: what did the user do to get here, and what are the conditions around this screen? |
Thanks @kushaldas I'll try to repro on |
This is for an edge case where the user is logged in 8+ hours and the token expires
So, for this first iteration we have two choices: (1) show an error message with the current way we show error messages in the login dialog, or (2) show no error message The problem I see with number 2 is that the user might not understand why we logged them out when their token expires.
I think we have an issue for creating a different log in screen for the sign in button: https://app.zeplin.io/project/5c807ea562f734bd2756b243/screen/5cf1b9bda9ebc81e5009b540: But we haven't yet collaborated on a design for token expiration yet. Let's discuss further in standup. |
For this edge case of session expiry, I like @creviera's solution a lot @ninavizz. I'd rather have a message than no message. And I don't think we're likely to prioritize in-client login soon, even post-beta -- there'll just be too many other requests and issues hitting us. I also don't know that it'd be a good idea to use here, even if we had it, given that the user may have stepped away, and may not expect content to be showing on the screen when they unlock the screensaver. I understand your point that this error style is intended for login errors. If you feel strongly that it should not be used here, one change we might still be able to make now is to style this message slightly differently (e.g., different color, icon). |
58b7ba2
to
7263eb3
Compare
@kushaldas, what you reported is indeed a bug on |
in case I missed it: for the fix in 7263eb3 - on master how does the api attribute on the controller become None prior to logout? (my expectation is that AuthError would get raised, but then logout would need to be called to set the api attribute to None) |
in widgets.py, when a user clicks on the logout button the controller logout function gets triggered which make an |
That was an explanation for the current code. For the code on in widgets.py, when a user clicks on the logout button the controller logout function gets triggered which make an |
7263eb3
to
a2550c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking good, some thoughts inline
self.on_logout_failure) | ||
self.api = None | ||
if self.api is not None: | ||
self.call_api(self.api.logout, self.on_logout_success, self.on_logout_failure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gotcha, so if self.api
is already None, we don't need to invalidate the token, because the token is already invalid/expired? (from inspecting the logic in on_sync_failure
and on_queue_paused
).
i think it would be good to write that this is what's going on in a comment somewhere (as it's only obvious from tracing the logic and could be easily missed in future modifications). alternatively we could move the setting of self.api = None
into this method and make it more explicit that this is what's happening via an invalidate_token
kwarg or something. Let me know if you have a better idea here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think only setting api to none in one place is a good idea. i was thinking about how I'd like to refactor how we check whether or not we're in offline_mode vs just offline (no token) but i think make a small change to only set api to None in one place would be a good addition to this pr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more on the whole offline-mode vs no-token: this is relevant if we want to provide a way for users to regenerate a new token without losing jobs in the api queue (so just pause and wait for a new token), but this is definitely a post-pilot issue
e0a39b1
to
ebc8df7
Compare
@@ -33,6 +33,9 @@ def call_api(self, api_client: API, session: Session) -> str: | |||
source = session.query(Source).filter_by(uuid=self.source_uuid).one() | |||
session.commit() | |||
|
|||
import time | |||
time.sleep(5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
snip out
retry=True) | ||
|
||
if isinstance(result, ApiInaccessibleError): | ||
self.invalidate_token() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very minor nit: maybe invalidate_token_locally
or something? (just because otherwise the dev might think this method calls Api.logout
)
Addressed PR comments and rebased on current |
one line uncovered so test job is failing, but otherwise lgtm from me. opened #761 to unpack the auth state tracking a bit more |
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies(). However, one or more SendReplyJobs (and their underlying POSTs requests to "/sources/<source_uuid/replies") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. It's up to a given SendReplyJob to raise an exception if it's failed; the state of a given DraftReply can't be determined, or meaningfully overriden, by an application-level event.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions.
…-level state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions. [WIP] refactor: consolidate mark_all_pending_drafts_as_failed() calls 1. when queue is cleared 2. when local storage updates after sync
…-level state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions. [WIP] refactor: consolidate mark_all_pending_drafts_as_failed() calls 1. when queue is cleared 2. when local storage updates after sync
…-level state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions. [WIP] refactor: consolidate mark_all_pending_drafts_as_failed() calls 1. when queue is cleared 2. when local storage updates after sync
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions.
… state Since #750, application-level state transitions (logging in, logging out, and switching into offline mode) call securedrop_client.storage.mark_all_pending_draft_replies_as_failed(). However, one or more SendReplyJobs (and their underlying POST requests to "/sources/<source_uuid/replies>") may be in flight on the network at that time, whether or not the application is connected (or even running) to receive their responses. Until we have better (ideally generalized) logic around upload jobs that have not yet been confirmed by the server, these application-level events should not make assumptions about the results of jobs that have already been dispatched. Individual SendReplyJobs still call their own _set_status_to_failed() method on non-timeout exceptions.
Description
Fixes #662
Fixes #736
Related: #420 remains open
Test Plan for #662
Test Plan for #736
/securedrop/journalist_app/api.py
in the securedrop repo to returnAuthError
when making aget_sources
orget_all_replies
orget_submissions
request and see the client log you out and open a login window, you can use this diff if you'd like, see how this results in anAuthError
:def get_all_sources(): + return jsonify({'error':'test', 'message':'test'})
Checklist