Skip to content

Fix review findings across auth, schema, and board state#2

Merged
fakechris merged 3 commits intomainfrom
fix/review-findings-auth-board
Apr 4, 2026
Merged

Fix review findings across auth, schema, and board state#2
fakechris merged 3 commits intomainfrom
fix/review-findings-auth-board

Conversation

@fakechris
Copy link
Copy Markdown
Owner

@fakechris fakechris commented Apr 4, 2026

Summary

  • harden auth token handling, add optional per-request viewer headers, and reuse shared CLI/env helpers
  • reduce GraphQL issue relation N+1 queries and normalize database URL parsing with the built-in URL API
  • replace BoardPage's full Apollo data copy with query data plus local overrides, and harden localStorage access in restricted contexts

Validation

  • pnpm --filter @involute/server typecheck
  • pnpm --filter @involute/server test --run src/auth.test.ts src/index.test.ts src/graphql-mutations.test.ts src/issues-filter.test.ts
  • pnpm --filter @involute/cli typecheck
  • pnpm --filter @involute/cli test --run src/commands/import.test.ts src/commands/verify.test.ts src/commands/issues.test.ts src/commands/comments.test.ts
  • pnpm --filter @involute/web typecheck
  • pnpm --filter @involute/web test --run

Open with Devin

Summary by CodeRabbit

Release Notes

  • New Features

    • CLI configuration now supports viewer-email and viewer-id settings for enhanced user identity management.
    • Request headers automatically include viewer identity and email information when configured.
  • Bug Fixes

    • Improved token comparison security using cryptographic timing-safe comparison.
    • Enhanced resilience in restricted browser contexts where local storage access may fail.
  • Refactor

    • Consolidated shared utility functions for cleaner code organization.
    • Improved board issue data handling and team key persistence.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Warning

Rate limit exceeded

@fakechris has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 44 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 44 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2710e65-1ea6-447a-8762-ab445fdb6af2

📥 Commits

Reviewing files that changed from the base of the PR and between 43cc284 and c163505.

📒 Files selected for processing (7)
  • packages/cli/src/index.ts
  • packages/server/src/auth.test.ts
  • packages/server/src/schema.ts
  • packages/web/src/board/utils.ts
  • packages/web/src/lib/apollo.tsx
  • packages/web/src/lib/storage.ts
  • packages/web/src/routes/BoardPage.tsx
📝 Walkthrough

Walkthrough

This PR reorganizes shared utility functions across CLI and server packages, adds viewer identity propagation through headers, enhances authentication with timing-safe token comparison, and improves web app state management. Changes span configuration extensions, database URL normalization, GraphQL schema improvements, and localStorage access safety.

Changes

Cohort / File(s) Summary
CLI shared utilities extraction
packages/cli/src/commands/shared.ts, packages/cli/src/commands/import.ts, packages/cli/src/commands/import.test.ts, packages/cli/src/commands/verify.ts
Extracted loadEnv, ensureDatabaseUrl, readJsonFile, and fileExists into new shared module; removed duplicate implementations from import.ts and verify.ts.
Server database URL normalization
packages/server/src/database-url.ts, packages/server/src/environment.ts, packages/server/prisma/env.ts
Extracted normalizeDatabaseUrl into dedicated module with safeDecode helper; updated imports in environment.ts and prisma/env.ts to use centralized implementation.
Authentication hardening and viewer identity
packages/server/src/auth.ts, packages/server/src/auth.test.ts
Added timing-safe token comparison via tokensMatch helper; extended createGraphQLContext to support x-involute-user-id and x-involute-user-email headers for dynamic viewer lookup; added comprehensive auth test suite.
CLI and server configuration for viewer identity
packages/cli/src/index.ts, packages/web/src/lib/apollo.tsx
Extended ConfigKey type and CliConfig interface to include viewer-email and viewer-id; added viewer header propagation in Apollo client with safe localStorage reads and environment variable precedence.
Web app localStorage safety and team persistence
packages/web/src/board/utils.ts, packages/web/src/App.drag-utils.test.tsx
Introduced readLocalStorageValue helper for safe reads in restricted contexts; added writeStoredTeamKey for persistent team selection; added test for localStorage failure resilience.
GraphQL schema and resolver optimization
packages/server/src/schema.ts, packages/server/src/issue-service.ts
Introduced buildIssueRelationInclude helper for centralized Prisma includes; added comment ordering configuration; updated resolvers to return enriched data with preloading optimization; refactored updateIssue to use in operator pattern.
Web board state management
packages/web/src/routes/BoardPage.tsx
Replaced single localIssues state with baseIssues, createdIssues, and issueOverrides model; implemented optimistic updates with reconciliation; added helpers for merging, filtering, and comparing issues.
Database schema
packages/server/prisma/schema.prisma
Removed explicit @default(now()) from Comment.updatedAt field, keeping only @updatedAt for automatic timestamping on updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Mission control done #1: Modifies the same CLI test imports and implements shared environment utility extraction across commands.

Poem

🐰 With whiskers twitching, I hop with glee,
Shared utilities dance wild and free!
Headers sprouted, state's now refined,
Safe storage reads calm the browser mind.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes across auth hardening, schema improvements (issue relations), and board state refactoring.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/review-findings-auth-board

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 and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors shared utilities across the CLI and server, centralizing environment loading and database URL normalization. It introduces support for viewer identification via custom headers in the CLI, server, and web client. The server-side GraphQL resolvers are optimized to pre-fetch relations and now utilize constant-time comparisons for authentication tokens. Additionally, the web client's board state management is improved with a reconciliation strategy for optimistic updates and local overrides, alongside safer localStorage access. Review feedback highlights a security improvement for token validation to prevent leaking secret lengths through timing attacks.

@@ -1,8 +1,14 @@
import { timingSafeEqual } from 'node:crypto';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

Import createHash from node:crypto to support a more secure token comparison method that avoids leaking the secret's length via timing.

Suggested change
import { timingSafeEqual } from 'node:crypto';
import { createHash, timingSafeEqual } from 'node:crypto';

Comment on lines +115 to +124
function tokensMatch(left: string, right: string): boolean {
const leftBuffer = Buffer.from(left, 'utf8');
const rightBuffer = Buffer.from(right, 'utf8');

if (leftBuffer.length !== rightBuffer.length) {
return false;
}

return timingSafeEqual(leftBuffer, rightBuffer);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The current implementation of tokensMatch leaks the length of the authToken because it returns false immediately if the lengths of the two buffers differ. An attacker can use this timing difference to determine the length of the secret token. To prevent this, hash both tokens using a fixed-length algorithm (like SHA-256) and then compare the resulting hashes using timingSafeEqual.

function tokensMatch(left: string, right: string): boolean {
  const leftHash = createHash('sha256').update(left).digest();
  const rightHash = createHash('sha256').update(right).digest();

  return timingSafeEqual(leftHash, rightHash);
}

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@fakechris fakechris merged commit 4981875 into main Apr 4, 2026
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +13 to +14
parsedUrl.username = safeDecode(parsedUrl.username);
parsedUrl.password = safeDecode(parsedUrl.password);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 normalizeDatabaseUrl corrupts URLs containing percent-encoded percent signs (%25)

The refactored normalizeDatabaseUrl using the URL API fails to properly round-trip %25 sequences (literal % characters) in usernames or passwords. When safeDecode decodes %25 to %, the URL API's setter does NOT re-encode % back to %25 because % (0x25) is not in the WHATWG URL spec's userinfo percent-encode set. This produces invalid percent-encoding in the output (e.g., p%word instead of p%25word), which can cause database connection failures.

Reproduction and behavioral difference

For input postgres://user:p%25word@host/db:

  • Old implementation (using encodeURIComponent): correctly outputs postgres://user:p%25word@host/db
  • New implementation (using URL API): incorrectly outputs postgres://user:p%word@host/db

The %wo in the output is not a valid percent-encoded triplet, which would likely cause PostgreSQL driver connection failures for any user whose database password contains a literal % character.

Prompt for agents
The issue is in the normalizeDatabaseUrl function in packages/server/src/database-url.ts. The URL API's username/password setters do not percent-encode the % character (because it's not in the WHATWG URL spec's userinfo percent-encode set). When safeDecode decodes %25 to %, and then the decoded value is assigned back via parsedUrl.username/password, the % is left as-is, producing invalid percent-encoding in the output.

Two approaches to fix:
1. Instead of using safeDecode + URL API setter, use encodeURIComponent(safeDecode(...)) to explicitly re-encode the decoded value, then manually reconstruct the URL string (similar to the old approach).
2. Keep the URL API approach but replace the URL API setter assignment with a manual encoding step: parsedUrl.username = encodeURIComponent(safeDecode(parsedUrl.username)) would not work directly since the setter double-encodes, but you could build the final URL string manually after parsing.

The simplest correct approach is to revert to using encodeURIComponent for the re-encoding step, since it correctly encodes % as %25, while still using the URL API for parsing the components.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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