You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the PrivacyGUI web app is served from the router itself (local build), the browser origin is the router's LAN IP. Client-side Remote Assistance (RA) requests that hit the cloud host directly are blocked by CORS. This is a confirmed, current-production problem.
1.x solved this by injecting a getHost closure into LinksysHttpClient (via the removed cloudRepositoryProvider) so client requests went through the router's /cloud/ reverse proxy (same-origin). That path is dead in 2.x: LinksysHttpClient.getHost / combineUrl still exist but combineUrl is never called, and every caller builds an absolute cloud URL itself, bypassing getHost.
Scope
RA only. Client-side RA requests proxy through the router on local builds; CA-side requests always go direct to cloud (the CA portal is cloud-hosted — proxying would misroute it). Firmware OTA and geolocation are explicitly out of scope.
Proposed solution
Add a small, testable host-resolution layer instead of restoring the dead getHost path:
kProxyPrefix constant (/cloud) — single point of change once FW confirms the prefix.
CloudHostResolver — decides the URL base to prepend to a Guardian endpoint:
CA-side → always direct cloud base.
Client-side → router proxy base (<origin>/cloud) iff local build AND origin resolvable; otherwise direct cloud (safe fallback).
Inject the resolver into GuardianApiClient and thread forCA through _buildUrl / _request, derived from sessionToken != null so it stays in lock-step with the auth-header branch (no drift possible).
Host source = Uri.base.origin (scheme + host + port). Chosen over window.location.host because same-origin/CORS requires the scheme to match; Uri.base.origin preserves it and is already an established pattern in this repo.
Acceptance Criteria
On a local build (BuildConfig.isLocal() == true), client-side RA requests (fetchDeviceToken, getSessions, getSessionInfo, createPin, deleteSession) target <Uri.base.origin>/cloud/v1/guardians/....
CA-side requests (getSessionInfoForCA, deleteSessionForCA) always target the cloud host directly, even on a local build.
On a non-local build, every RA request (client + CA) targets the cloud host directly — behavior byte-for-byte identical to today.
When the origin is unresolvable (empty), client-side requests fall back to the cloud host rather than failing.
Uri.base.origin scheme is preserved (http stays http, port preserved) — no forced https://.
The proxy path prefix is a single configurable constant (kProxyPrefix); changing the FW prefix requires no other code change.
Existing RA flows (dialog, banner, CA confirm view) work unchanged; remote_assistance_service_test.dart stays green.
Blocked / open items
FW proxy prefix unconfirmed — kProxyPrefix is one constant; flip the value when FW lands. This blocks on-device sign-off, not implementation. (label: need-fw-confirm)
E2E mock builds are local (isLocal() == true) → client RA calls take the proxy path; if the E2E harness mocks Guardian at the network boundary, confirm it intercepts /cloud/... URLs too.
Problem
When the PrivacyGUI web app is served from the router itself (local build), the browser origin is the router's LAN IP. Client-side Remote Assistance (RA) requests that hit the cloud host directly are blocked by CORS. This is a confirmed, current-production problem.
1.x solved this by injecting a
getHostclosure intoLinksysHttpClient(via the removedcloudRepositoryProvider) so client requests went through the router's/cloud/reverse proxy (same-origin). That path is dead in 2.x:LinksysHttpClient.getHost/combineUrlstill exist butcombineUrlis never called, and every caller builds an absolute cloud URL itself, bypassinggetHost.Scope
RA only. Client-side RA requests proxy through the router on local builds; CA-side requests always go direct to cloud (the CA portal is cloud-hosted — proxying would misroute it). Firmware OTA and geolocation are explicitly out of scope.
Proposed solution
Add a small, testable host-resolution layer instead of restoring the dead
getHostpath:kProxyPrefixconstant (/cloud) — single point of change once FW confirms the prefix.CloudHostResolver— decides the URL base to prepend to a Guardian endpoint:<origin>/cloud) iff local build AND origin resolvable; otherwise direct cloud (safe fallback).GuardianApiClientand threadforCAthrough_buildUrl/_request, derived fromsessionToken != nullso it stays in lock-step with the auth-header branch (no drift possible).Uri.base.origin(scheme + host + port). Chosen overwindow.location.hostbecause same-origin/CORS requires the scheme to match;Uri.base.originpreserves it and is already an established pattern in this repo.Acceptance Criteria
BuildConfig.isLocal() == true), client-side RA requests (fetchDeviceToken,getSessions,getSessionInfo,createPin,deleteSession) target<Uri.base.origin>/cloud/v1/guardians/....getSessionInfoForCA,deleteSessionForCA) always target the cloud host directly, even on a local build.Uri.base.originscheme is preserved (http stays http, port preserved) — no forcedhttps://.kProxyPrefix); changing the FW prefix requires no other code change.remote_assistance_service_test.dartstays green.Blocked / open items
kProxyPrefixis one constant; flip the value when FW lands. This blocks on-device sign-off, not implementation. (label:need-fw-confirm)isLocal() == true) → client RA calls take the proxy path; if the E2E harness mocks Guardian at the network boundary, confirm it intercepts/cloud/...URLs too.Tests
test/core/cloud/cloud_host_resolver_test.dart— resolver logic (7 cases: local+client, local+CA, empty-origin fallback, non-local, port preserved, http scheme preserved, default constructor).test/core/cloud/guardian_api_client_test.dart— URL assembly (client methods → proxy URL incl. session-id substitution; CA methods → cloud URL under the same resolver).test/core/cloud/services/remote_assistance_service_test.dart— regression, stays green unchanged.