Skip to content

feat: unified token management with Flow auto-refresh - #74

Open
rafaga2469 wants to merge 4 commits into
ryaker:mainfrom
rafaga2469:feat/flow-auth-migration-upstream
Open

feat: unified token management with Flow auto-refresh#74
rafaga2469 wants to merge 4 commits into
ryaker:mainfrom
rafaga2469:feat/flow-auth-migration-upstream

Conversation

@rafaga2469

@rafaga2469 rafaga2469 commented Jul 29, 2026

Copy link
Copy Markdown

Summary

This PR combines 3 related improvements to the OAuth token system, each building on the previous:

1. Fix persistent OAuth authentication

Token refresh requested a downscoped token (3 scopes) vs the original 10, causing silent scope downgrade and forced re-auth every few hours. Unifies all scope lists to a single source in config.js.

2. Migrate power-automate handlers to TokenStorage

5 Power Automate handlers imported getFlowAccessToken from �uth/token-manager.js (sync I/O, no refresh). Migrated to TokenStorage with async I/O and 4 new Flow methods. Removed Flow methods from oken-manager.js (kept createTestTokens for test mode).

3. Add Flow token auto-refresh

getValidFlowAccessToken() returned null on expiry with no refresh attempt. Added
efreshFlowAccessToken() mirroring the Graph refresh pattern with flowRefreshPromise dedup. Surgical invalidation on failure: nulls only low* keys, preserves Graph tokens.

Files Changed (16 files, ~844 lines)

Area Files Change
Auth core config.js, �uth/token-storage.js, �uth/tools.js, �uth/index.js, �uth/token-manager.js, outlook-auth-server.js Unified scopes, Flow methods, Flow auto-refresh, deprecation
Power Automate power-automate/*.js (5 files) Migrated imports to TokenStorage
Tests est/auth/*.js (4 files), est/power-automate/handlers.test.js 27 new tests

Testing

  • 175 tests passing (148 original + 27 new)
  • 0 ESLint errors
  • Backwards compatible: token file format unchanged, existing tokens work
  • No handler changes needed for auto-refresh (already calling getValidFlowAccessToken())

Backwards Compatibility

  • Token file format unchanged ( low_access_token, low_refresh_token, low_expires_at keys)
  • Existing token files continue to work
  • No data migration needed
  • oken-manager.js retained for createTestTokens() (test mode)

Summary by CodeRabbit

  • New Features

    • Added a shared token storage component for Microsoft Graph and Power Automate flows.
    • Expanded OAuth scopes to include offline access and Contacts access, driven by configuration.
    • Enhanced email folder lookup to support slash-delimited paths (segment resolution).
  • Bug Fixes

    • Improved token refresh concurrency, timeout behavior, and error handling for both Graph and Power Automate flows.
    • Strengthened token persistence and cache load reliability.
  • Tests

    • Expanded Jest coverage for token storage exports, Graph/flow token refresh flows, authentication status checks, Power Automate handlers, and folder resolution.

The token refresh mechanism was silently downscoping tokens from 10
scopes to 3 (offline_access User.Read Mail.Read), causing permission
loss and forced re-authentication every few hours.

- Unify all 4 scope lists to single source of truth in config.js
  (10 scopes including offline_access + Contacts.Read)
- token-storage.js now imports scopes from config.js instead of
  hardcoded 3-scope string
- MS_SCOPES env var override warns if offline_access is missing
- outlook-auth-server.js imports scopes from config.js
- check-auth-status migrated from legacy token-manager to TokenStorage
  (reports authenticated when token is expired but refreshable)
- 5 new tests covering scope unification, MS_SCOPES validation, and
  full-scope refresh (147/147 total passing)
…Storage

- Add 4 Flow token methods to TokenStorage: getFlowAccessToken,
  saveFlowTokens, isFlowTokenExpired, getValidFlowAccessToken
- Export tokenStorage singleton from auth/index.js
- Migrate 5 power-automate handlers to async TokenStorage calls
- Remove Flow methods from token-manager.js (keep createTestTokens only)
- 18 new tests (166 total, all passing)
- SDD cycle: explore → propose → spec → design → tasks → apply → verify → archive
- Add refreshFlowAccessToken() mirroring Graph refresh with Flow scope
- Update getValidFlowAccessToken() to attempt refresh on expiry (was null)
- Add _flowRefreshPromise for concurrent refresh dedup
- Surgical invalidation on failure: null only flow_* keys, preserve Graph tokens
- 9 new tests (175 total, all passing)
- SDD cycle: explore -> propose -> spec -> design -> tasks -> apply -> verify -> archive
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codeant-ai

codeant-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

User does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR centralizes Graph and Power Automate token handling in TokenStorage, adds Flow-token refresh and persistence behavior, updates OAuth configuration, removes legacy Flow helpers, migrates authentication consumers, and adds hierarchical mail-folder path resolution.

Changes

Authentication and folder resolution updates

Layer / File(s) Summary
OAuth configuration and token lifecycle
config.js, outlook-auth-server.js, auth/token-storage.js, test/auth/token-storage.test.js
OAuth settings and scopes are configuration-driven; TokenStorage manages Graph and Flow token refresh, persistence, expiry validation, authorization-code exchange, request handling, and related coverage.
Authentication exports and consumer migration
auth/index.js, auth/token-manager.js, auth/tools.js, power-automate/*.js, test/auth/*, test/power-automate/handlers.test.js
The shared tokenStorage instance is exported, legacy Flow helpers are removed, and authentication consumers retrieve validated tokens asynchronously.
Hierarchical mail-folder resolution
email/folder-utils.js
Folder lookup resolves slash-delimited names through parent and child-folder Graph queries with exact and case-insensitive matching.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Handler
  participant TokenStorage
  participant OAuthEndpoint
  participant TokenFile
  Handler->>TokenStorage: Request valid Flow access token
  TokenStorage->>OAuthEndpoint: Refresh when token is expired
  OAuthEndpoint-->>TokenStorage: Return token response
  TokenStorage->>TokenFile: Persist refreshed or invalidated Flow fields
  TokenStorage-->>Handler: Return token or null
Loading

Possibly related PRs

Suggested labels: size:XXL

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: centralized token management with automatic Flow token refresh.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
test/auth/token-storage.test.js (1)

66-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move MS_SCOPES cleanup into afterEach. A failed assertion skips the trailing delete, leaking the env var into subsequent tests (including the default-scopes test at line 55) and producing misleading cascading failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/auth/token-storage.test.js` around lines 66 - 92, Move MS_SCOPES
environment cleanup from the individual tests into an afterEach hook for the
TokenStorage test suite, ensuring it always deletes the variable even when
assertions fail. Remove the duplicate inline cleanup statements while preserving
both MS_SCOPES test behaviors.
test/auth/tools.test.js (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mock the singleton contract used by production.

The test mocks TokenStorage as a constructor, so it reinforces per-request instantiation instead of validating shared tokenStorage usage. Mock ../../auth/index and assert calls to the singleton’s getValidAccessToken() method.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/auth/tools.test.js` around lines 1 - 4, Update the auth tools test setup
to mock ../../auth/index rather than TokenStorage, matching production’s shared
tokenStorage singleton contract. In the tests around handleCheckAuthStatus,
assert interactions with the singleton’s getValidAccessToken() method and remove
constructor-based TokenStorage mocking or expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/token-storage.js`:
- Around line 136-141: Update the token assignment in the flow token storage
logic to preserve the existing flow_refresh_token when flowTokens.refresh_token
is undefined, matching the guard already used by refreshFlowAccessToken. Only
replace the stored refresh token when a new value is provided; keep the
access-token and expiry updates unchanged.
- Around line 452-460: Update the token exchange error handling around the
rejection in auth/token-storage.js to remove raw data from both console.error
and the rejected Error message. Replace the payload with safe metadata such as
response length and HTTP status, while preserving the original processing error
and existing rejection behavior.
- Around line 19-22: Update the token store configuration to reuse
appConfig.AUTH_CONFIG.tokenStorePath instead of constructing a path from HOME or
USERPROFILE. Preserve the shared configured path consumed by
outlook-auth-server.js and token-manager.js, including config.js fallbacks when
both environment variables are unset.
- Around line 233-234: Update the https.request calls in auth/token-storage.js
at lines 233-234 and 320-321, used by the Graph refresh and flow refresh paths,
to apply a timeout that destroys the request socket so the existing error
handlers clear _refreshPromise and _flowRefreshPromise. Also add the same
timeout to exchangeCodeForTokens at lines 415-416, preserving its existing error
handling while ensuring its non-cached request cannot hang indefinitely.
- Around line 369-383: Preserve flow credentials for transient failures: remove
flow-token invalidation from the request error handler in the flow refresh
logic, and update the non-2xx response branch to invalidate only permanent OAuth
failures such as 400 invalid_grant, not 429/5xx responses. Also adjust
getValidFlowAccessToken so it does not clear tokens for arbitrary refresh
rejections, while retaining invalidation for invalid_grant and the existing
rejection/promise cleanup behavior.

In `@auth/tools.js`:
- Around line 59-70: The authentication-status check in auth/tools.js should
reuse the shared token storage exported by auth/index.js instead of constructing
a new TokenStorage instance; update the imports and the status-check flow
accordingly. In test/auth/tools.test.js, mock the auth/index.js singleton export
rather than the TokenStorage constructor, while preserving the existing
authentication-status assertions.

---

Nitpick comments:
In `@test/auth/token-storage.test.js`:
- Around line 66-92: Move MS_SCOPES environment cleanup from the individual
tests into an afterEach hook for the TokenStorage test suite, ensuring it always
deletes the variable even when assertions fail. Remove the duplicate inline
cleanup statements while preserving both MS_SCOPES test behaviors.

In `@test/auth/tools.test.js`:
- Around line 1-4: Update the auth tools test setup to mock ../../auth/index
rather than TokenStorage, matching production’s shared tokenStorage singleton
contract. In the tests around handleCheckAuthStatus, assert interactions with
the singleton’s getValidAccessToken() method and remove constructor-based
TokenStorage mocking or expectations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b72f22d-f02e-4dde-b931-b1a1c7d480e8

📥 Commits

Reviewing files that changed from the base of the PR and between 95d6ff2 and 62e3c93.

📒 Files selected for processing (16)
  • auth/index.js
  • auth/token-manager.js
  • auth/token-storage.js
  • auth/tools.js
  • config.js
  • outlook-auth-server.js
  • power-automate/list-environments.js
  • power-automate/list-flows.js
  • power-automate/list-runs.js
  • power-automate/run-flow.js
  • power-automate/toggle-flow.js
  • test/auth/index.test.js
  • test/auth/token-manager.test.js
  • test/auth/token-storage.test.js
  • test/auth/tools.test.js
  • test/power-automate/handlers.test.js

Comment thread auth/token-storage.js
Comment on lines +19 to +22
tokenStorePath: path.join(
process.env.HOME || process.env.USERPROFILE,
'.outlook-mcp-tokens.json'
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reuse appConfig.AUTH_CONFIG.tokenStorePath instead of re-deriving it. config.js (line 8) falls back through HOME → USERPROFILE → os.homedir() → '/tmp', while this derivation throws TypeError: path argument must be of type string when both env vars are unset (common under launchd/systemd). It also creates a second source of truth for the token file that outlook-auth-server.js and token-manager.js both read from config.

🛠️ Proposed fix
-      tokenStorePath: path.join(
-        process.env.HOME || process.env.USERPROFILE,
-        '.outlook-mcp-tokens.json'
-      ),
+      tokenStorePath: appConfig.AUTH_CONFIG.tokenStorePath,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tokenStorePath: path.join(
process.env.HOME || process.env.USERPROFILE,
'.outlook-mcp-tokens.json'
),
tokenStorePath: appConfig.AUTH_CONFIG.tokenStorePath,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/token-storage.js` around lines 19 - 22, Update the token store
configuration to reuse appConfig.AUTH_CONFIG.tokenStorePath instead of
constructing a path from HOME or USERPROFILE. Preserve the shared configured
path consumed by outlook-auth-server.js and token-manager.js, including
config.js fallbacks when both environment variables are unset.

Comment thread auth/token-storage.js
Comment thread auth/token-storage.js
Comment thread auth/token-storage.js Outdated
Comment thread auth/token-storage.js
Comment thread auth/tools.js
HIGH severity:
- Escape apostrophes in OData filter for folder names (folder-utils.js)
- Preserve flow_refresh_token in saveFlowTokens when undefined
- Add 30s timeout to all https.request calls (refresh, flow refresh, exchange)
- Only invalidate flow tokens on permanent failures (invalid_grant), not
  transient (429/5xx/network)

MEDIUM severity:
- Use singleton tokenStorage from auth/index.js in check-auth-status
- Remove raw response data from exchangeCodeForTokens error messages
- Update warning message to mention both credential pairs

212 tests passing, 0 ESLint errors
@codeant-ai

codeant-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

User does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
auth/token-storage.js (1)

75-77: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

{ mode: 0o600 } only applies when the file is created. Existing token files (created before this change, typically 0644) keep their permissions, so credentials stay world-readable after upgrade. Chmod explicitly after write.

🔒 Proposed fix
       await fs.writeFile(this.config.tokenStorePath, JSON.stringify(this.tokens, null, 2), {
         mode: 0o600,
       });
+      await fs.chmod(this.config.tokenStorePath, 0o600);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/token-storage.js` around lines 75 - 77, Update the token persistence
flow around the writeFile call to explicitly set the token store permissions to
0o600 after writing, including for existing files. Preserve the current path and
JSON serialization while ensuring chmod applies on every save.
🧹 Nitpick comments (1)
auth/token-storage.js (1)

157-179: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Permanent-failure detection via refreshError.message.includes('invalid_grant') is fragile. refreshFlowAccessToken rejects with responseBody.error_description (e.g. AADSTS700082: The refresh token has expired…), which typically does not contain the string invalid_grant, so this branch rarely fires. Net behavior is still correct only because refreshFlowAccessToken already invalidates flow tokens internally for 400 + invalid_grant. Consider dropping the duplicated invalidation here, or attaching a structured flag (e.g. error.oauthError = responseBody.error) at the rejection site and checking that instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/token-storage.js` around lines 157 - 179, The permanent-failure check in
the flow-token refresh catch block is unreliable because it inspects only
refreshError.message. Update refreshFlowAccessToken and its caller to use the
structured OAuth error response, such as attaching responseBody.error to the
rejected error and checking that flag in the catch, or remove the duplicated
invalidation if refreshFlowAccessToken already handles all permanent
invalid_grant failures. Preserve invalidation for permanent failures without
invalidating tokens on transient errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@email/folder-utils.js`:
- Around line 76-78: Update the case-insensitive fallback in resolveFolderPath
to paginate through all Graph folder pages instead of searching only the initial
response from callGraphAPI. Repeatedly follow the response’s `@odata.nextLink`
using callGraphAPI until a matching folder is found or no next link remains,
preserving the existing match and Inbox fallback behavior.

---

Outside diff comments:
In `@auth/token-storage.js`:
- Around line 75-77: Update the token persistence flow around the writeFile call
to explicitly set the token store permissions to 0o600 after writing, including
for existing files. Preserve the current path and JSON serialization while
ensuring chmod applies on every save.

---

Nitpick comments:
In `@auth/token-storage.js`:
- Around line 157-179: The permanent-failure check in the flow-token refresh
catch block is unreliable because it inspects only refreshError.message. Update
refreshFlowAccessToken and its caller to use the structured OAuth error
response, such as attaching responseBody.error to the rejected error and
checking that flag in the catch, or remove the duplicated invalidation if
refreshFlowAccessToken already handles all permanent invalid_grant failures.
Preserve invalidation for permanent failures without invalidating tokens on
transient errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9cb90766-1ec7-48d6-b3a5-9f4be8cfced8

📥 Commits

Reviewing files that changed from the base of the PR and between 62e3c93 and cac655d.

📒 Files selected for processing (5)
  • auth/token-storage.js
  • auth/tools.js
  • email/folder-utils.js
  • test/auth/token-storage.test.js
  • test/auth/tools.test.js

Comment thread email/folder-utils.js
Comment on lines +76 to +78
// If exact match fails, try to get all folders and do a case-insensitive comparison
const allFoldersResponse = await callGraphAPI(accessToken, 'GET', base, null, { $top: 100 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Paginate the case-insensitive fallback.

Only the first 100 folders are searched. A matching folder on a later Graph page is treated as absent, causing resolveFolderPath to fall back to Inbox. Follow @odata.nextLink until a match is found or pages are exhausted; callGraphAPI already supports full next-link URLs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@email/folder-utils.js` around lines 76 - 78, Update the case-insensitive
fallback in resolveFolderPath to paginate through all Graph folder pages instead
of searching only the initial response from callGraphAPI. Repeatedly follow the
response’s `@odata.nextLink` using callGraphAPI until a matching folder is found
or no next link remains, preserving the existing match and Inbox fallback
behavior.

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