Skip to content

refactor: extract encryptTokenPair utility#584

Merged
ColeMurray merged 2 commits intomainfrom
refactor/extract-encrypt-token-pair
May 2, 2026
Merged

refactor: extract encryptTokenPair utility#584
ColeMurray merged 2 commits intomainfrom
refactor/extract-encrypt-token-pair

Conversation

@ColeMurray
Copy link
Copy Markdown
Owner

@ColeMurray ColeMurray commented May 2, 2026

Summary

  • Extracts a shared encryptTokenPair(accessToken, refreshToken, encryptionKey) utility in auth/crypto.ts that encrypts an access/refresh token pair, returning null for unprovided tokens and throwing on encryption failure
  • Replaces 3 inline try/catch encryption blocks in router.ts (handleCreateSession and handleSessionWsToken) with calls to the shared utility
  • Callers decide error policy: handleCreateSession catches and returns 500, handleSessionWsToken catches and continues with null tokens

Test plan

  • 5 new unit tests for encryptTokenPair covering: both undefined, access-only, refresh-only, both provided (with round-trip decryption verification), and invalid key (throws)
  • All 1039 existing control-plane tests pass
  • Typecheck passes
  • Lint passes

Summary by CodeRabbit

  • Bug Fixes

    • Unified encryption of access and refresh tokens into a single token-pair flow and tightened error handling so encryption failures now surface as errors instead of being partially ignored.
  • Tests

    • Added tests covering token-pair encryption, missing-token cases, successful encrypt/decrypt, and failure/error scenarios.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6d5bfb4-1a26-44bd-a07d-780a1ca20860

📥 Commits

Reviewing files that changed from the base of the PR and between e62c6da and c127b10.

📒 Files selected for processing (1)
  • packages/control-plane/src/router.ts

📝 Walkthrough

Walkthrough

Adds a new exported helper encryptTokenPair(accessToken, refreshToken, encryptionKey) that conditionally encrypts access and refresh tokens (returning null for undefined inputs). Router handlers now call this single helper and fail the request on encryption errors. Tests added for the new helper.

Changes

Token Pair Encryption Consolidation

Layer / File(s) Summary
Data Shape / API
packages/control-plane/src/auth/crypto.ts
New exported encryptTokenPair(accessToken, refreshToken, encryptionKey) returning { accessTokenEncrypted: string | null, refreshTokenEncrypted: string | null }.
Core Implementation
packages/control-plane/src/auth/crypto.ts
Implements conditional calls to existing encryptToken for each token; maps undefined inputs to null; propagates encryption errors.
Integration / Wiring
packages/control-plane/src/router.ts
Replaces independent access/refresh encryption with a single encryptTokenPair(...) call in handleCreateSession and handleSessionWsToken; handlers now return 500 on pair-encryption failures.
Tests / Validation
packages/control-plane/src/auth/crypto.test.ts
Adds describe("encryptTokenPair") tests covering both-undefined, single-token, both-tokens encryption/decryption, and failure on invalid key.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Router as Router\n(handleCreateSession / handleSessionWsToken)
    participant Crypto as Crypto\n(encryptTokenPair)
    participant Metrics as Metrics

    Client->>Router: POST /create-session or WS token request (scmToken, scmRefreshToken)
    Router->>Metrics: start timer (optional)
    Router->>Crypto: encryptTokenPair(scmToken, scmRefreshToken, TOKEN_ENCRYPTION_KEY)
    Crypto-->>Router: { accessTokenEncrypted, refreshTokenEncrypted } or throws
    Router->>Metrics: stop timer (optional)
    Router-->>Client: 200 with encrypted tokens OR 500 on encryption error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • open-inspect

Poem

🐰
I nibbled code beneath the moonlit glade,
Two tokens met and joined in one parade.
Encrypt we hop, together side by side,
If one should fail, we tumble with our pride.
A happy hop for crypto, now well-played.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor: extract encryptTokenPair utility' directly describes the main change: extracting a new shared encryption utility function.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/extract-encrypt-token-pair

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
Review rate limit: 6/8 reviews remaining, refill in 7 minutes and 54 seconds.

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Comment thread packages/control-plane/src/router.ts
open-inspect[bot]
open-inspect Bot previously requested changes May 2, 2026
Copy link
Copy Markdown
Contributor

@open-inspect open-inspect Bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR extracts a shared encryptTokenPair helper and rewires the two router call sites to use it. The helper itself is straightforward and the new unit tests are useful, but the handleCreateSession refactor changes existing error-handling semantics in a way that looks like a behavioral regression.

  • PR Title and number: refactor: extract encryptTokenPair utility (#584)
  • Author: @ColeMurray
  • Files changed count: 3
  • Additions/deletions: +96 / -38

Critical Issues

  • [Functionality] packages/control-plane/src/router.ts:930 - handleCreateSession used to degrade gracefully when refresh-token encryption failed: it logged a warning and still created the session without refresh support. After this refactor, any failure inside encryptTokenPair() causes a 500 response and aborts session creation. Because the PR is positioned as a refactor/extraction, this looks like an unintended behavior change. Preserving the prior split policy here would avoid rejecting otherwise valid session creation requests.

Suggestions

  • [Testing] packages/control-plane/src/router.ts:924 - Once the create-session behavior is restored, a targeted test around the router path would help pin the intended asymmetry: access-token encryption failure is fatal, refresh-token encryption failure is non-fatal.

Nitpicks

  • Nit: packages/control-plane/src/auth/crypto.ts:113 - The doc comment says the helper returns null for tokens that were not provided (undefined/empty), but the signature only accepts string | undefined. If empty strings are intentionally part of the contract, a small test would make that explicit; otherwise the comment could just say undefined/falsy inputs.

Positive Feedback

  • The extracted helper removes duplicated encryption flow and keeps the return shape explicit at the call sites.
  • The new encryptTokenPair unit tests cover the main success cases plus an invalid-key failure path.
  • handleSessionWsToken remains easy to read after the refactor, especially with the early return when no encryption key is configured.

Questions

None.

Verdict

Request Changes: the handleCreateSession refactor currently changes refresh-token encryption failures from a warning-only degradation into a hard 500.

@ColeMurray
Copy link
Copy Markdown
Owner Author

Good catch on the behavior change. We reviewed this and are comfortable with it — TOKEN_ENCRYPTION_KEY has been present in all deployments for a long time, and an encryption failure at this point would indicate a misconfiguration or a broken key rather than a transient issue worth degrading through. If encryptToken fails for the refresh token, it would almost certainly fail for the access token too (same key, same algorithm), so the graceful degradation path was effectively dead code.

Keeping both fatal simplifies the contract: encryptTokenPair either succeeds or throws, and callers get a single error policy decision rather than per-token semantics.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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)
packages/control-plane/src/router.ts (1)

927-955: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve caller-provided SCM tokens during enrichment.

encryptTokenPair can now return one encrypted value and one null. The existing if (!scmTokenEncrypted) fallback then overwrites the whole pair, so a refresh-only or access-only request can lose the token the caller actually supplied. Backfill each field independently instead of gating on the access-token result.

🔧 Suggested fix
-        if (!scmTokenEncrypted) {
-          scmTokenEncrypted = enrichment.accessTokenEncrypted ?? null;
-          scmRefreshTokenEncrypted = enrichment.refreshTokenEncrypted ?? null;
-          scmTokenExpiresAt = enrichment.tokenExpiresAt;
-        }
+        scmTokenEncrypted ??= enrichment.accessTokenEncrypted ?? null;
+        scmRefreshTokenEncrypted ??= enrichment.refreshTokenEncrypted ?? null;
+        scmTokenExpiresAt ??= enrichment.tokenExpiresAt;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/control-plane/src/router.ts` around lines 927 - 955, The enrichment
block currently replaces both token fields only when scmTokenEncrypted is falsy,
which overwrites a caller-supplied counterpart if encryptTokenPair returned one
encrypted value and one null; change the logic in the resolveGitHubEnrichment
handling so you backfill each token independently (e.g., set scmTokenEncrypted
only if it's null/undefined using enrichment.accessTokenEncrypted, set
scmRefreshTokenEncrypted only if it's null/undefined using
enrichment.refreshTokenEncrypted, and similarly backfill scmTokenExpiresAt from
enrichment.tokenExpiresAt) instead of wrapping both assignments in the single if
(!scmTokenEncrypted) branch; keep resolveGitHubEnrichment, scmTokenEncrypted,
scmRefreshTokenEncrypted, and scmTokenExpiresAt as the referenced symbols to
locate and update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/control-plane/src/router.ts`:
- Around line 1723-1740: The current catch in the encrypt block swallows
encryption failures and returns {scmTokenEncrypted: null,
scmRefreshTokenEncrypted: null}, which can overwrite valid tokens in the Session
DO; instead, change the error handling in the encryptTokenPair call so that if
env.TOKEN_ENCRYPTION_KEY is present and encryptTokenPair throws, the error is
rethrown (or propagate the original error) rather than returning nulls. Locate
the block that calls encryptTokenPair and replace the catch-return-null behavior
(and its logger.error usage) with logic that logs the error and then throws (or
rethrows) the error so the request fails upstream; keep the early return of
nulls only for the case when env.TOKEN_ENCRYPTION_KEY is falsy.

---

Outside diff comments:
In `@packages/control-plane/src/router.ts`:
- Around line 927-955: The enrichment block currently replaces both token fields
only when scmTokenEncrypted is falsy, which overwrites a caller-supplied
counterpart if encryptTokenPair returned one encrypted value and one null;
change the logic in the resolveGitHubEnrichment handling so you backfill each
token independently (e.g., set scmTokenEncrypted only if it's null/undefined
using enrichment.accessTokenEncrypted, set scmRefreshTokenEncrypted only if it's
null/undefined using enrichment.refreshTokenEncrypted, and similarly backfill
scmTokenExpiresAt from enrichment.tokenExpiresAt) instead of wrapping both
assignments in the single if (!scmTokenEncrypted) branch; keep
resolveGitHubEnrichment, scmTokenEncrypted, scmRefreshTokenEncrypted, and
scmTokenExpiresAt as the referenced symbols to locate and update.
🪄 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

Run ID: af1b471a-0081-4a2d-aea3-30168470705b

📥 Commits

Reviewing files that changed from the base of the PR and between 6073d58 and e62c6da.

📒 Files selected for processing (3)
  • packages/control-plane/src/auth/crypto.test.ts
  • packages/control-plane/src/auth/crypto.ts
  • packages/control-plane/src/router.ts

Comment thread packages/control-plane/src/router.ts Outdated
Make handleSessionWsToken consistent with handleCreateSession: if
TOKEN_ENCRYPTION_KEY is present and encryptTokenPair throws, return
500 instead of silently continuing with null tokens.
@ColeMurray
Copy link
Copy Markdown
Owner Author

Agreed — updated in c127b10. handleSessionWsToken now returns 500 on encryption failure instead of silently continuing with null tokens, matching handleCreateSession's behavior.

The "overwrite valid tokens" concern doesn't actually apply here (the DO's ws-token handler checks clientSentAnyToken and skips the update when both are null), but returning nulls and silently degrading is still the wrong behavior when the encryption key is present and broken.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@ColeMurray ColeMurray merged commit ad7f4ea into main May 2, 2026
30 of 31 checks passed
@ColeMurray ColeMurray deleted the refactor/extract-encrypt-token-pair branch May 2, 2026 06:16
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