Skip to content

feat(security): let a consumer supply the access token via setAccessTokenResolver - #329

Open
gcutrini wants to merge 6 commits into
mainfrom
feat/access-token-resolver-5x
Open

feat(security): let a consumer supply the access token via setAccessTokenResolver#329
gcutrini wants to merge 6 commits into
mainfrom
feat/access-token-resolver-5x

Conversation

@gcutrini

@gcutrini gcutrini commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

ref: https://app.clickup.com/t/86bbnquuq

5.x line companion of #324 (same change on the v4.x line).

uicore's getAccessToken assumes the access token lives on the client. It reads and refreshes the token from local storage and serializes refreshes with navigator.locks. That model does not fit a server-rendered host such as the Next.js event site, where the token is held in an encrypted httpOnly cookie and is never exposed to JavaScript. There is no client-side token for uicore to read, lock, or renew.

Keeping the token out of JavaScript is also a deliberate security property. A token that never reaches the browser's JS context cannot be stolen by XSS or replayed from client code.

Several uicore modules call getAccessToken, so the source must be injectable inside uicore rather than overridden by the consumer. Today a cookie-based host has to work around this by aliasing security/methods at build time and substituting its own implementation, which is invasive (build-system specific, replaces the whole module) and easy to drift out of sync.

What

Add setAccessTokenResolver to security/methods: a consumer registers where the access token comes from, without replacing anything.

  • It does not bypass getAccessToken. When a resolver is registered, getAccessToken delegates to it; otherwise the built-in flow is unchanged. Passing a non-function resets to the built-in.
  • It is opt-in. Existing consumers such as summit-admin are unaffected; there is no behavior change unless they call setAccessTokenResolver.

Renewal is the consumer's concern

When a resolver is registered, uicore delegates token retrieval to it and does not apply its own storage, locking, or renewal to the returned value. Freshness is left entirely to the consumer. A cookie-based host, for example, renews server-side (the server refreshes the session cookie via the refresh_token grant, and proxied API calls attach the real bearer from the cookie) and hands uicore only a "session present" placeholder, so there is nothing on the client to lock or renew.

Implementation note

The resolver is module-level state, so security/methods is also externalized in the webpack build. That way every uicore lib entry shares one methods instance and sees the registered resolver; otherwise an entry that inlines its own copy keeps its own null resolver and ignores it.

…okenResolver

Add setAccessTokenResolver: when a resolver is registered, getAccessToken delegates to it; otherwise the built-in flow is unchanged. Passing a non-function resets to the built-in.

The resolver is module-level state, so externalize security/methods in the webpack build so every uicore lib entry shares one instance and sees the registered resolver.
Delegates to a registered resolver, a later resolver replaces the previous one, and a non-function argument clears it.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 85818af3-5df9-4aa2-8432-9f2546833535


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gcutrini
gcutrini requested a review from smarcet August 27, 2026 17:02
_fetch rejected its promise after already calling back with the error.
The query* functions never await that promise, so the rejection only
surfaced as an unhandled rejection. Return instead. Also guard the 404
branch so a missing response or callback cannot throw.
With a resolver registered, query-actions, getUserInfo and
AttendanceTracker send the resolver's token, and a resolver failure in
query-actions reaches the caller's callback without a request.
methods.js only needed the SET_LOGGED_USER string, but importing it from
actions.js pulled the whole actions module (and utils/actions) into the
methods bundle, and actions.js imports methods back. With security/methods
externalized, that inlined copy resolved to the methods lib file itself.
Move the string to constants.js; actions.js keeps re-exporting it.
Move the remaining security action types and session-state statuses next
to SET_LOGGED_USER and re-export them from actions.js, so existing imports
keep working. reducers.js and utils/actions.js read them from constants,
so they no longer inline the actions module.
@gcutrini

Copy link
Copy Markdown
Contributor Author

Note on the externals rule added here.

security/methods.js imported the SET_LOGGED_USER string from ./actions, and actions.js imports ./methods back. That import cycle already existed on v4.x and was harmless: the methods bundle just carried its own inlined copy of actions and methods.

This PR externalizes security/methods so every bundle shares one instance (needed for the resolver state). With that rule, the inlined actions copy inside the methods bundle resolved to lib/security/methods.js itself, so the file required itself. It still loaded (nothing reads it at load time), but the inlined copies would see an empty module, and a consumer bundling uicore from source with esbuild can fail to resolve it. So it had to be fixed here, not later.

Fix: the action-type strings moved to security/constants.js; actions.js re-exports them, so existing imports keep working. reducers.js and utils/actions.js also read from constants now, so they stop inlining the actions module. No API change.

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.

1 participant