-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Forcibly reload app from server when API is redirected #3286
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for actualbudget ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Bundle Stats — desktop-clientHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset
View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger
Smaller No assets were smaller Unchanged
|
Bundle Stats — loot-coreHey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle. As this PR is updated, I'll keep you updated on how the bundle size is impacted. Total
Changeset
View detailed bundle breakdownAdded No assets were added Removed No assets were removed Bigger
Smaller No assets were smaller Unchanged No assets were unchanged |
I found this because I have the same issue with Authelia and Actual. |
Could we do something like this? I run silverbullet as well and it seems like they were able to solve the issue by just setting |
Reloading when an API query gets a 302 would work for my setup (and I'd guess many other people's), however there are probably many valid setups where a 302 is normal and the correct action is to have the API follow the redirect and return the redirected response normally. Can we be confident that any time a 302 is returned that we need to reload? Maybe a configuration setting which has this automatic reloading as an optional feature? |
Maybe an option where you can configure hosts that if we get redirected to one of those hosts we reload? In this case any time I get a redirect to auth.mydomain.com I want to reload |
I don't see why any config would be necessary. If you make a request to a trusted destination and the request is calling for you to be redirected, you should redirect. You don't even need to handle reloading I think. As long as your proxy sends a redirect call, it should just directly redirect you without prompting. That's how normal webpages without service workers would react anyways. |
Just glacing over the source I see this which appears to let you wrap the fetch command. Line 32 in 9c0e6a3
|
A whitelist would also be an option but as this is a common problem with reverse proxies + auth, I think having to first figure out what's wrong and then find the solution would be a bit annoying to users. As I see it, there are two common behaviors that can happen when you get a 302:
Another point to consider is that some auth solutions pass the return URL to the auth page. |
I'm happy to implement doing this automatically when an API request gets a 302 if maintainers agree that is appropriate. My initial design was erring on the side of not breaking existing setups.
The problem is only api requests get redirected. GET requests for the page itself get served by the service worker with cached code and therefore do not get redirected.
I agree, we do not want to follow the 302 that originates from the API request because after authentication finishes we'd be redirected back to the api call; not the app. Instead we'd want to force a uncached GET the current page and follow the resulting redirect.
The way I've figured out how to do that is by unregistering the navigation service worker and reloading. This is what I do in my |
I think the approach needed here would be to change the service worker logic to stale-while-revalidate, which causes it to still fetch in the background if there is a connection. That way we can handle the background fetch and redirect if it returns 302. I'm on board with just redirecting unconditionally for now; if someone does start using a setup where the redirect isn't an auth request, we can help them at that time. Another thing worth noting is that IIRC one of the first things the app does when it loads is make an API request to check if the server has been initialized. Assuming my memory is correct, maybe handling only API requests is fine? |
In this case, nothing happens since the redirect url cannot be determined.
It's probably better to use network first. Stale while revalidate would work but only on the second request since it pulls from the cache first. |
Looking at the config, it seems like no runtime caching is being performed right now. Presumably, all you would need to do is handle the redirect that the service worker returns. A slight disadvantage is the return url would likely be an API call. I suppose if you unregister the service worker and remake the request, that will direct the user to the right place. I'm just not sure of the implications of unregistering the service worker. https://github.com/actualbudget/actual/blob/master/packages/desktop-client/vite.config.mts#L155 |
I've added functionality to automatically reload when an API call gets redirected. Please let me know if there's anything to improve |
Is the reload button on settings even needed still? Can that be removed? |
Yes. I've now removed the setting and updated the release notes. |
LGTM but I'll let someone with more familiarity take a look at the code. The PR description also need an update as well. |
hi, this pr would be incredibly useful to me as I also run mine behind an authenticated gateway. if I can be of help here, I'd be happy to- but otherwise I'm just chiming in with thanks to @TimQuelch |
feel free to try it out and even put in a reviews, but you are right that a maintainer has to sign it off to merge in. |
At this point it is ready for review and just waiting for a maintainer to have the time to take a look. I've been running this branch on my instance for several weeks now with no issues. |
Is there an easy way to run this in container or do you need to run an entire build process to get that? |
This is the dockerfile that I use to run the actual server with specific versions of actual. Modify the commit arg to the git commit you want to run. https://gist.github.com/TimQuelch/8f794cf187eeabb24d6eaee278e3ba38 It is somewhat based on the the actual server edge images https://github.com/actualbudget/actual-server/blob/master/docker/edge-alpine.Dockerfile |
@coderabbitai full review |
Actions performedFull review triggered. |
WalkthroughThe pull request introduces several changes aimed at enhancing the application's ability to manage state and handle API interactions more effectively. Key updates include the addition of a Changes
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 0
Outside diff range and nitpick comments (2)
packages/loot-core/src/types/server-events.d.ts (1)
21-21
: LGTM! Consider using a more specific type for the event payload.The addition of the
'api-fetch-redirected'
event type enhances the server's ability to handle and respond to various states of API interactions, potentially improving the robustness of the event handling system.However, consider using a more specific type for the event payload if possible. This can help catch type-related issues at compile time and provide better type safety in the event handlers.
packages/loot-core/src/platform/server/fetch/index.web.ts (1)
3-19
: LGTM! The customfetch
function is a great addition to enhance error handling and monitoring.The custom
fetch
function is well-structured and follows best practices for error handling and monitoring. It detects and handles API request redirection effectively, allowing for better debugging and security awareness in the application.A few additional suggestions for improvement:
- Consider adding a configuration option to enable or disable the redirection handling logic. This would provide flexibility for different setups and allow users to opt-out if needed.
- Consider logging the redirection details (original URL, redirected URL, etc.) for easier debugging and tracking.
- Consider adding tests to cover the redirection handling logic and ensure it works as expected.
Overall, great work on this enhancement!
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files ignored due to path filters (1)
upcoming-release-notes/3286.md
is excluded by!**/*.md
Files selected for processing (6)
- packages/desktop-client/src/browser-preload.browser.js (1 hunks)
- packages/desktop-client/src/global-events.ts (1 hunks)
- packages/loot-core/src/client/actions/app.ts (1 hunks)
- packages/loot-core/src/platform/server/fetch/index.web.ts (1 hunks)
- packages/loot-core/src/types/server-events.d.ts (1 hunks)
- packages/loot-core/typings/window.d.ts (1 hunks)
Additional comments not posted (4)
packages/loot-core/typings/window.d.ts (1)
18-18
: LGTM! The optionalreload
method is a valuable addition.The introduction of the optional
reload
method to the globalActual
object aligns well with the PR objectives of enhancing the application's ability to manage state and handle API interactions more effectively. By returning aPromise<void>
, the method allows for better control flow when handling reloads that may involve asynchronous tasks, such as refreshing data or reinitializing components.The optional nature of the
reload
method provides flexibility for different environments or configurations, while its asynchronous behavior contributes to a more dynamic and responsive application. This change enables developers to handle reloads in a structured manner, improving the overall user experience.packages/loot-core/src/client/actions/app.ts (1)
50-54
: LGTM! The newreloadApp
function looks good and serves its purpose.The
reloadApp
function provides a way to reload the application by invoking thereload
method on the globalActual
object. This change is part of a larger effort to handle authentication issues when using forward authentication with proxies like Caddy or Traefik.Based on the PR objectives and comments, this function was initially introduced along with a button in the advanced settings to unregister the service worker and reload the page. However, the implementation was later adjusted to automatically reload the application upon receiving a 302 response from any API call, with the aim of improving user experience without breaking existing setups.
It's important to consider the broader context and potential impact of this change on the application, especially in terms of handling redirects and ensuring that users are not inadvertently redirected back to an API call after authentication.
Overall, the
reloadApp
function itself looks good and serves its purpose within the larger scope of the PR.packages/desktop-client/src/global-events.ts (1)
157-159
: Verify if the automatic reloading behavior is configurable or has safeguards.The implementation of the event listener for
api-fetch-redirected
looks good and aligns with the PR objective of automatically reloading the app when an API request is redirected.However, as discussed in the PR comments, automatic reloading on a 302 response may not be suitable for all setups. Additionally, there is a risk of inadvertently redirecting users back to an API call after authentication if not handled carefully.
Please verify the following:
- Is there a configuration option to manage the automatic reloading behavior?
- Are there safeguards in place to prevent unintended redirects, such as a whitelist for trusted redirect URLs or modifications to the service worker logic to use a "network first" strategy?
You can use the following script to search for relevant code:
packages/desktop-client/src/browser-preload.browser.js (1)
54-68
: LGTM!The
reload
method is a well-designed addition to theglobal.Actual
object. It effectively handles the unregistration of the service worker and reloads the application to ensure fresh data is fetched from the server.The code segment demonstrates the following positive aspects:
- Graceful degradation by checking for service worker support before proceeding with the unregistration process.
- Proper use of promises and chaining to ensure the unregistration is complete before triggering the page reload.
- Clear and concise code structure that is easy to understand and maintain.
Overall, this change enhances the application's ability to refresh its state and content, particularly in scenarios where updates or changes on the server need to be reflected immediately in the client without interference from the service worker.
One concern I thought of with this approach is that the user could get stuck in a reload loop if the server sends a 302 to only API resources (if someone forgets to put the JS bundle behind a proxy). Maybe that is not a realistic scenario but thought it was worth mentioning the possibility |
This adds a wrapper around fetch so the application is reloaded when an API request is redirected. The motivation for this is as a workaround for the bug in #2793.
This issue occurs when using forward authentication with something like authentik with a proxy (e.g. caddy, traefik). The proxy checks with the auth service whether all requests to the host are valid, if they are they are forwarded onto the backend server (in this case actual server). If the request is invalid e.g. the cookie has expired or the user has not authenticated yet, then the proxy redirects the user to the authentication page. After authenticating they are redirected back to what they were originally requesting.
For a more standard application; after a authenticatoin cookie has expired the next time the page is reloaded it would redirect to the auth page in order for the user to re-authenticate. However actual uses a service worker pattern where the application code is cached and all requests are served by the worker. This means that reloading the page in the browser does not actually result in a http request to the backend server; which means that that request cannot be redirected to authenticate. This results in an invalid authentication session and API queries to the backend failling because they are redirecting to auth pages.
This PR adds a button in the advanced settings section that un-registers the service worker handling the routing and then reloads the page. This forces the page to be requeried from the backend server which is then able to be redirected to the authentication page.I've tested this functionality on web, mobile browser, and also iphone PWA and it works as expected.
I've got a couple things I want to check on my implementation:
This functionality isn't relevant on electron. I have followed the pattern that the ResetSync follows by disabling the button if we're on electron. I considered also completely hiding the setting when on electron. Which would be preferable?I couldn't think of a solution that was less of a workaround than this. Maybe this reloading could be triggered if an API query gets a 302 to a configurable path? e.g if/sync/sync
gets redirected uselessly toauth.mydomain.com
then we trigger reloading the whole app so that the user can re-auth? I feel I don't know the full consequences of this solution though. Does anyone have any thoughts on a more automatic solution that will work in a general and/or configurable way?Edit: After feedback I re-implemented the solution to wrap the API fetch so that if any API call is redirected the application is reloaded.