Skip to content

OCD-5430: continued removal of AngularJS stuff - #1531

Merged
kekey1 merged 30 commits into
chpladmin:stagingfrom
andlar:OCD-5430
Sep 16, 2026
Merged

kekey1 merged 30 commits into
chpladmin:stagingfrom
andlar:OCD-5430

Conversation

@andlar

@andlar andlar commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What

Removes the AngularJS authService and networkService and moves session state
into the Redux store, with axios-jwt owning token storage. 29 commits, 0 behind
staging.

  • New services/auth.service.js — the single copy of hasAnyRole(user, roles)
    (previously duplicated in index.run.js and user-wrapper.jsx) and
    clearSession(dispatch, removeCookie), which tears down cookies, tokens and
    user state together. All three logout paths now go through it.
  • userInfo slice reducers are pure again — the ngStorage-* migration moved
    to store.js, which now also purges the legacy jwtToken/refreshToken keys
    that no longer had an owner. setUser(user) plus a separate clearUser()
    replaces the ambiguous setUser({ user }) / setUser(undefined) pair.
  • CookiesProvider moved above ApiWrapper and UserWrapper so session
    cookies are set and cleared through one set of options.
  • One Redux store shared across entry bundles, held on window.chplStore.
  • Download and upload links refresh an expired access token via
    useFreshAccessToken() (refreshTokenIfNeeded) instead of getAccessToken(),
    which only read storage. All nine call sites now report and bail rather than
    sending Bearer undefined.
  • Housekeeping picked up along the way: yarn 4.17.1 → 4.18.0 (yarn.lock
    unchanged, so inert), a new CLAUDE.md, and removal of the unused
    MINUTES_UNTIL_IDLE/MINUTES_UNTIL_LOGOUT build globals. The yarn release file
    is ~1,950 lines of the diff.

Please look closely at

store.js — the store on window. index.html injects all 11 webpack entry
chunks and each compiled its own copy of store.js, so a page held ten independent
stores. UserWrapper used to hide this by re-reading localStorage on mount and
listening for $rootScope loggedIn/loggedOut; removing authService took that
away, 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 only
appeared 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: undefined in the second,
subscriber never fired.

Cookie clearing. user-wrapper.jsx previously sat above CookiesProvider, so
its useCookies fell back to react-cookie's default context instance, which has no
defaultSetOptions. removeCookie('refresh_token') therefore serialized without
domain/path and never deleted the .healthit.gov; path=/ cookie — it only worked
because authService.logout() also issued an explicit document.cookie expiry.
defaultSetOptions is 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 the refresh_token cookie is not
httpOnly.

For awareness rather than action here: these download URLs carry the token as an
authorization=Bearer%20… query parameter, which predates this branch. Because the
token 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.js has the same impure-reducer pattern, with comments reading
    "temporary until redux store is truly global" — a premise this PR resolves.
  • ng-idle stays for now: it provides the Title service the chpl run block
    injects. Replacing that with a React/TitleProvider equivalent would let the
    dependency go.

🤖 Generated with Claude Code

@andlar
andlar requested a review from kekey1 August 24, 2026 16:14
andlar and others added 23 commits August 24, 2026 12:51
…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
andlar marked this pull request as ready for review September 8, 2026 16:58
@kekey1
kekey1 merged commit 601bb0e into chpladmin:staging Sep 16, 2026
2 checks passed
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