OCD-5430: continued removal of AngularJS stuff - #1531
Merged
Merged
Conversation
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
[#OCD-5430]
…e breaks the rules of hooks [#OCD-5430]
…service
- Add services/auth.service.js holding the single copy of hasAnyRole and a
clearSession that tears down cookies, tokens and user state together;
index.run.js and user-wrapper.jsx no longer carry their own hasAnyRole
- Keep the userInfo reducers pure by moving the ngStorage-currentUser
migration into store.js, where chplState is already the persisted copy
- Replace setUser({ user }) / setUser(undefined) with setUser(user) and a
separate clearUser action
- Render CookiesProvider above ApiWrapper and UserWrapper so session cookies
are always set and cleared through the provider's options
- Guard against an absent user when refreshing the access token, and only
clear the session on an invalid token when someone is actually signed in
- Read state.userInfo.user in force-change-password so the password strength
meter sees the user's own details again
[#OCD-5430]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ates index.html loads every webpack entry bundle and each one compiled its own copy of store.js, so a page held ten independent stores. UserWrapper used to paper over this by re-reading localStorage on mount and listening for $rootScope 'loggedIn'/'loggedOut'; removing authService took that away, leaving a login visible only to whichever bundle rendered the form. Cold start still worked because every store preloads from chplState, so the breakage only appeared on a login or logout after page load. Hold the store on window instead, so all bundles share one instance and react-redux notifies subscribers in every mounted tree. [#OCD-5430] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…links
getAccessToken only reads storage, so the upload and download paths that pass a
token in a query string or an upload header failed once the access token had
expired, even though the app still looked signed in. Use axios-jwt's
refreshTokenIfNeeded instead, which refreshes a stale token first.
Lift requestRefresh out of the AxiosProvider useMemo into useRequestRefresh so
the request interceptor and the links share one definition, and expose it as
useFreshAccessToken. That hook absorbs the errors refreshTokenIfNeeded throws
when there is no session left to refresh ('No refresh token available', and its
own complaint when requestRefresh returns nothing after clearing the session),
so these click handlers don't become unhandled rejections.
[#OCD-5430]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ewed - Report the failure once in useFreshAccessToken and return early at all nine call sites, instead of building a Bearer undefined URL that was guaranteed to fail with no explanation; upload-listing also clears its processing state - Add the missing apiKey dependency to the api and download page effects, both of which build URLs that embed it and so could keep a stale key - Replace the duplicated 'Surveillance (Basic)' comparisons with a restrictedOptions map that states the role gate and the token requirement in one place - Drop the downloadLink state and effect from questionable-activity-view and build the URL in the handler, as developers-view already does [#OCD-5430] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
index.run.js and upload-real-world-testing.jsx picked up the executable bit while being edited; put them back to 100644 to match staging. [#OCD-5430] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
authService wrote ngStorage-jwtToken and ngStorage-refreshToken alongside ngStorage-currentUser, and staging removed all three on logout. The migration only dropped the user key, leaving a stale refresh token behind for anyone mid-session at deploy. Sweep all three in createStore instead, so the copy is gone on first load rather than at a logout that may never happen. [#OCD-5430] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MINUTES_UNTIL_IDLE and MINUTES_UNTIL_LOGOUT were injected into every bundle but referenced nowhere in src, and nothing configures IdleProvider or KeepaliveProvider — so the config implied an idle logout the app no longer has. Update the CLAUDE.md list to match. ng-idle itself has to stay: it provides the Title service that the chpl run block injects. That is not apparent from where the module is declared, so note it on the dependency. [#OCD-5430] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> [#OCD-5430]
andlar
marked this pull request as ready for review
September 8, 2026 16:58
kekey1
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Removes the AngularJS
authServiceandnetworkServiceand moves session stateinto the Redux store, with
axios-jwtowning token storage. 29 commits, 0 behindstaging.services/auth.service.js— the single copy ofhasAnyRole(user, roles)(previously duplicated in
index.run.jsanduser-wrapper.jsx) andclearSession(dispatch, removeCookie), which tears down cookies, tokens anduser state together. All three logout paths now go through it.
userInfoslice reducers are pure again — thengStorage-*migration movedto
store.js, which now also purges the legacyjwtToken/refreshTokenkeysthat no longer had an owner.
setUser(user)plus a separateclearUser()replaces the ambiguous
setUser({ user })/setUser(undefined)pair.CookiesProvidermoved aboveApiWrapperandUserWrapperso sessioncookies are set and cleared through one set of options.
window.chplStore.useFreshAccessToken()(refreshTokenIfNeeded) instead ofgetAccessToken(),which only read storage. All nine call sites now report and bail rather than
sending
Bearer undefined.yarn.lockunchanged, so inert), a new
CLAUDE.md, and removal of the unusedMINUTES_UNTIL_IDLE/MINUTES_UNTIL_LOGOUTbuild globals. The yarn release fileis ~1,950 lines of the diff.
Please look closely at
store.js— the store onwindow.index.htmlinjects all 11 webpack entrychunks and each compiled its own copy of
store.js, so a page held ten independentstores.
UserWrapperused to hide this by re-reading localStorage on mount andlistening for
$rootScopeloggedIn/loggedOut; removingauthServicetook thataway, leaving a login visible only to the bundle that rendered the form. Cold start
still worked because every store preloads from
chplState, so the breakage onlyappeared on a login or logout after page load — and a refresh hid it.
Verified by loading the real module under three independent registries: 1 instance,
a dispatch through one visible in the others, a subscriber in a third notified. With
the global cleared (pre-fix): 3 instances,
user: undefinedin the second,subscriber never fired.
Cookie clearing.
user-wrapper.jsxpreviously sat aboveCookiesProvider, soits
useCookiesfell back to react-cookie's default context instance, which has nodefaultSetOptions.removeCookie('refresh_token')therefore serialized withoutdomain/pathand never deleted the.healthit.gov; path=/cookie — it only workedbecause
authService.logout()also issued an explicitdocument.cookieexpiry.defaultSetOptionsis unchanged; only the provider's position moved.Security review
Focused review of the diff: no HIGH or MEDIUM findings introduced. One candidate
was raised and rejected — the migration now purges the legacy token keys, but the
underlying exposure (refresh token readable from localStorage by same-origin JS) is
pre-existing and by design in
axios-jwt, plus therefresh_tokencookie is nothttpOnly.For awareness rather than action here: these download URLs carry the token as an
authorization=Bearer%20…query parameter, which predates this branch. Because thetoken is now guaranteed valid where it was often expired, the value of that
pre-existing surface (history, address bar, proxy logs) goes up. A short-lived
download token or POST/blob download is the real fix.
Follow-ups (not in this PR)
browserInfo.slice.jshas the same impure-reducer pattern, with comments reading"temporary until redux store is truly global" — a premise this PR resolves.
ng-idlestays for now: it provides theTitleservice thechplrun blockinjects. Replacing that with a React/
TitleProviderequivalent would let thedependency go.
🤖 Generated with Claude Code