Skip to content

feat(security): Phase 2 opaque token architecture - #187

Merged
2witstudios merged 8 commits into
masterfrom
feat/phase2-opaque-tokens
Jan 13, 2026
Merged

2witstudios merged 8 commits into
masterfrom
feat/phase2-opaque-tokens

Conversation

@2witstudios

@2witstudios 2witstudios commented Jan 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Complete migration from JWT service tokens to opaque session tokens for the processor service:

  • EnforcedAuthContext: New immutable auth context class that can only be constructed from validated sessions
  • Processor middleware rewrite: Now uses sessionService.validateSession() instead of JWT verification
  • Token creation migration: validated-service-token.ts now creates opaque sessions via sessionService.createSession()
  • Schema update: Added driveId column to sessions table for processor validation
  • Legacy removal: Deleted service-auth.ts and all JWT service token code

Breaking Changes

  • Processor now requires opaque tokens (ps_svc_*) instead of JWT tokens
  • req.serviceAuth replaced with req.auth (EnforcedAuthContext)

Test plan

  • All unit tests pass
  • TypeScript compilation clean
  • Full test suite passes (8/8 tasks)
  • Manual testing with real upload flow
  • Verify processor accepts new opaque tokens
  • Run database migration in staging

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Migrated to a session-based authorization model, unifying auth handling and permission checks across endpoints.
  • New Features
    • Sessions can include an optional drive association for finer resource binding and rate-limiting.
    • Broadcast requests now use signed headers for delivery.
  • Bug Fixes / UX
    • Stricter upload validations and clearer authentication/error messages.
    • Rate-limiting now prefers user-based keys when signed-in.
  • Tests
    • Added coverage for the new auth context; legacy service-token tests removed.
  • Database
    • Migration adds drive_id to sessions.

✏️ Tip: You can customize this high-level summary in your review settings.

NOTE: Not done yet: user auth/refresh remains JWT-based, realtime has a JWT fallback for desktop, and desktop WS auth still uses JWT.
Full legacy JWT deprecation is tracked in plan P5-T5.

Complete migration from JWT service tokens to opaque session tokens:

- Add EnforcedAuthContext class for immutable auth context from validated sessions
- Update processor middleware to use sessionService.validateSession()
- Migrate validated-service-token.ts to use sessionService.createSession()
- Add driveId column to sessions schema for processor validation
- Update all processor API endpoints to use req.auth (EnforcedAuthContext)
- Remove legacy JWT service-auth.ts and related code

Breaking changes:
- Processor now requires opaque tokens (ps_svc_*) instead of JWT tokens
- req.serviceAuth replaced with req.auth (EnforcedAuthContext)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jan 13, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Replaces JWT service-token auth with a session-based model: middleware validates sessions via sessionService, attaches an immutable EnforcedAuthContext on req.auth, APIs switched from req.serviceAuth/hasServiceScope to req.auth/hasAuthScope, sessions gain optional driveId, and legacy service-auth code/tests removed.

Changes

Cohort / File(s) Summary
Core auth middleware & types
apps/processor/src/middleware/auth.ts, apps/processor/src/types/express.d.ts, apps/processor/src/middleware/rate-limit.ts
Replace claims/service-token flows with session validation; export EnforcedAuthContext; attach req.auth; update helpers (requireScope(scope: string), hasAuthScope, getUserId); rate-limit bucket keys use auth.userId when present.
Processor API endpoints
apps/processor/src/api/*
apps/processor/src/api/avatar.ts, apps/processor/src/api/ingest.ts, apps/processor/src/api/optimize.ts, apps/processor/src/api/serve.ts, apps/processor/src/api/upload.ts
Switch req.serviceAuth → req.auth and hasServiceScope → hasAuthScope; upload.ts heavily revised to use auth.resourceBinding/driveId/userId, stricter validation and error messages, metadata writes use auth.userId and service='processor', and improved temp-file cleanup and logging.
Enforced auth context & tests
packages/lib/src/permissions/enforced-context.ts, packages/lib/src/permissions/__tests__/enforced-context.test.ts, packages/lib/src/permissions/index.ts
Add immutable EnforcedAuthContext with scope matching (exact, namespace, wildcard), admin and resource-binding semantics; unit tests added; re-export from permissions index.
Session service & auth utilities
packages/lib/src/auth/session-service.ts, packages/lib/src/auth/auth-utils.ts, packages/lib/src/index.ts
Add optional driveId to session claims/create options and surface; remove legacy service-token helpers/exports; public API now exposes sessionService and EnforcedAuthContext.
Validated service-token creation
packages/lib/src/services/validated-service-token.ts, packages/lib/src/services/__tests__/validated-service-token.test.ts
Token creation now uses sessionService.createSession (payload: expiresInMs, resourceType/resourceId, driveId, createdByService); add ServiceScope type and durationToMs; tests adapted to mock session service.
Removed legacy service-auth & tests
packages/lib/src/services/service-auth.ts, packages/lib/src/services/__tests__/service-auth.test.ts
Remove JWT-based service-auth implementation (create/verify, JTI/Redis logic) and its test suite.
Database schema & migration
packages/db/src/schema/sessions.ts, packages/db/drizzle/0037_slimy_bishop.sql, packages/db/drizzle/meta/_journal.json
Add drive_id / driveId column to sessions table and include migration SQL and journal entry.
Tests & test infra updates
packages/db/src/__tests__/sessions-schema.test.ts, packages/lib/src/__tests__/notifications.test.ts
Tests updated for safer setup/teardown, unique IDs, defensive cleanup, and adjusted notification test imports/setup.
Notifications broadcast signing
packages/lib/src/notifications/notifications.ts
Use createSignedBroadcastHeaders(requestBody) and a precomputed requestBody string for signed broadcast fetch calls.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ProcessorAPI
    participant AuthMiddleware
    participant SessionService
    participant Database

    Client->>ProcessorAPI: HTTP request with session token
    ProcessorAPI->>AuthMiddleware: authenticateService(req)
    AuthMiddleware->>SessionService: validateSession(token)
    SessionService->>Database: SELECT session by tokenHash
    Database-->>SessionService: session row (userId, scopes, resourceBinding, driveId)
    SessionService-->>AuthMiddleware: SessionClaims
    AuthMiddleware->>AuthMiddleware: EnforcedAuthContext.fromSession(claims)
    AuthMiddleware-->>ProcessorAPI: attach req.auth
    ProcessorAPI->>ProcessorAPI: authorize via req.auth.hasScope / isBoundToResource
    ProcessorAPI-->>Client: 200 or 401/403
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through tokens, left the old behind,

Sessions snug and scopes all neatly signed,
DriveId tucked gently under my paw,
Auths now binding where they ought to draw,
A merry hop — secure trails I find.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% 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
Title check ✅ Passed The PR title accurately summarizes the main objective: migrating from JWT service tokens to opaque session tokens (Phase 2 of token architecture), which is the core change across all modified files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @apps/processor/src/api/ingest.ts:
- Around line 13-20: The two auth checks in ingest.ts are returning inconsistent
messages: change the first check that currently does res.status(401).json({
error: 'Authentication required' }) so it uses the same string as the second
check (res.status(401).json({ error: 'Service authentication required' }));
update the error literal where req.auth is falsy (and ensure you leave the
subsequent userId check and returns intact) so both checks use "Service
authentication required" for consistent API responses.
🧹 Nitpick comments (4)
packages/lib/src/services/__tests__/validated-service-token.test.ts (1)

505-508: Minor: Redundant mock clearing.

vi.clearAllMocks() already clears all mocks including mockFindFirst, making the explicit mockFindFirst.mockClear() call redundant.

♻️ Simplify beforeEach
   beforeEach(() => {
     vi.clearAllMocks();
-    mockFindFirst.mockClear();
   });
apps/processor/src/api/optimize.ts (1)

136-136: Consider replacing any type with a proper interface.

Per coding guidelines, avoid any types. While this line wasn't changed in this PR, consider defining a type for the results object:

🔧 Suggested type definition
interface BatchPresetResult {
  cached: boolean;
  url?: string;
  jobId?: string;
  status: 'completed' | 'queued';
  error?: string;
}

const results: Record<string, BatchPresetResult> = {};
packages/lib/src/permissions/__tests__/enforced-context.test.ts (1)

18-25: Private constructor test doesn't verify runtime behavior.

The test defines attemptConstruction but never calls it. While TypeScript enforcement is the primary goal, the comment on line 23 suggests runtime behavior should be tested. Consider either:

  1. Actually invoking the function to document what happens at runtime, or
  2. Removing the runtime comment since TS compilation is the intended enforcement
Option 1: Invoke and document runtime behavior
     it('cannot be constructed directly (TypeScript enforced)', () => {
       // TypeScript prevents direct construction via private constructor
-      // This test documents the design intent - TS compilation enforces it
       // @ts-expect-error - Constructor is private and inaccessible
       const attemptConstruction = () => new EnforcedAuthContext('u', 'user', [], undefined);
-      // At runtime JS allows it, but TS prevents compilation
-      expect(typeof attemptConstruction).toBe('function');
+      // At runtime JS allows it since private is a TS-only concept
+      const context = attemptConstruction();
+      expect(context).toBeDefined();
     });
packages/lib/src/auth/session-service.ts (1)

101-105: Consider logging lastUsedAt update failures for observability.

The non-blocking update pattern is appropriate for performance, but silently swallowing errors could mask persistent database issues.

🔧 Optional: Add debug-level logging
     // Update last used (non-blocking)
     db.update(sessions)
       .set({ lastUsedAt: new Date() })
       .where(eq(sessions.tokenHash, tokenHash))
-      .catch(() => {});
+      .catch((err) => {
+        // Log at debug level - not critical but useful for monitoring
+        if (process.env.NODE_ENV !== 'production') {
+          console.debug('Failed to update session lastUsedAt:', err);
+        }
+      });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0125609 and 6f89746.

📒 Files selected for processing (22)
  • apps/processor/src/api/avatar.ts
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/api/optimize.ts
  • apps/processor/src/api/serve.ts
  • apps/processor/src/api/upload.ts
  • apps/processor/src/middleware/auth.ts
  • apps/processor/src/middleware/rate-limit.ts
  • apps/processor/src/types/express.d.ts
  • packages/db/drizzle/0037_slimy_bishop.sql
  • packages/db/drizzle/meta/0037_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/sessions.ts
  • packages/lib/src/auth/auth-utils.ts
  • packages/lib/src/auth/session-service.ts
  • packages/lib/src/index.ts
  • packages/lib/src/permissions/__tests__/enforced-context.test.ts
  • packages/lib/src/permissions/enforced-context.ts
  • packages/lib/src/permissions/index.ts
  • packages/lib/src/services/__tests__/service-auth.test.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • packages/lib/src/services/service-auth.ts
  • packages/lib/src/services/validated-service-token.ts
💤 Files with no reviewable changes (3)
  • packages/lib/src/auth/auth-utils.ts
  • packages/lib/src/services/tests/service-auth.test.ts
  • packages/lib/src/services/service-auth.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • apps/processor/src/api/serve.ts
  • apps/processor/src/types/express.d.ts
  • packages/lib/src/permissions/enforced-context.ts
  • packages/lib/src/permissions/index.ts
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/api/optimize.ts
  • apps/processor/src/middleware/rate-limit.ts
  • packages/lib/src/index.ts
  • packages/lib/src/auth/session-service.ts
  • packages/db/src/schema/sessions.ts
  • apps/processor/src/api/avatar.ts
  • packages/lib/src/permissions/__tests__/enforced-context.test.ts
  • apps/processor/src/api/upload.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/middleware/auth.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: React hook files should use camelCase matching the exported hook name (e.g., useAuth.ts)
Zustand store files should use camelCase with use prefix (e.g., useAuthStore.ts)

Files:

  • apps/processor/src/api/serve.ts
  • apps/processor/src/types/express.d.ts
  • packages/lib/src/permissions/enforced-context.ts
  • packages/lib/src/permissions/index.ts
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/api/optimize.ts
  • apps/processor/src/middleware/rate-limit.ts
  • packages/lib/src/index.ts
  • packages/lib/src/auth/session-service.ts
  • packages/db/src/schema/sessions.ts
  • apps/processor/src/api/avatar.ts
  • packages/lib/src/permissions/__tests__/enforced-context.test.ts
  • apps/processor/src/api/upload.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/middleware/auth.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • apps/processor/src/api/serve.ts
  • apps/processor/src/types/express.d.ts
  • packages/lib/src/permissions/enforced-context.ts
  • packages/lib/src/permissions/index.ts
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/api/optimize.ts
  • apps/processor/src/middleware/rate-limit.ts
  • packages/lib/src/index.ts
  • packages/db/drizzle/meta/_journal.json
  • packages/lib/src/auth/session-service.ts
  • packages/db/src/schema/sessions.ts
  • apps/processor/src/api/avatar.ts
  • packages/lib/src/permissions/__tests__/enforced-context.test.ts
  • apps/processor/src/api/upload.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/middleware/auth.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for database queries with PostgreSQL

Files:

  • packages/db/src/schema/sessions.ts
packages/db/src/schema/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Database schema changes must be made in packages/db/src/schema/ and then pnpm db:generate must be run to create migrations

Files:

  • packages/db/src/schema/sessions.ts
**/*auth*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*auth*.{ts,tsx}: Use custom JWT authentication with jose library for user management
Use bcryptjs for password hashing

Files:

  • apps/processor/src/middleware/auth.ts
🧠 Learnings (8)
📚 Learning: 2025-12-22T20:04:40.910Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to **/*.{ts,tsx} : Use centralized permission functions from `pagespace/lib/permissions` (e.g., `getUserAccessLevel`, `canUserEditPage`) instead of implementing permission logic locally

Applied to files:

  • packages/lib/src/permissions/index.ts
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/api/upload.ts
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to app/**/*.{ts,tsx} : Import and use `getUserAccessLevel()` and `canUserEditPage()` from `pagespace/lib/permissions` for centralized permission logic

Applied to files:

  • packages/lib/src/permissions/index.ts
📚 Learning: 2025-12-23T18:49:41.966Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.966Z
Learning: Applies to apps/web/src/app/**/route.{ts,tsx} : For permission logic, use centralized functions from `pagespace/lib/permissions`: `getUserAccessLevel()`, `canUserEditPage()`

Applied to files:

  • apps/processor/src/api/ingest.ts
📚 Learning: 2025-12-22T20:04:40.910Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to packages/db/src/schema.ts : Update database schema in `packages/db/src/schema.ts` and generate migrations with `pnpm db:generate`

Applied to files:

  • packages/db/src/schema/sessions.ts
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to packages/db/src/schema.ts : Maintain the Drizzle ORM database schema in `packages/db/src/schema.ts` as the single entry point for schema definitions

Applied to files:

  • packages/db/src/schema/sessions.ts
📚 Learning: 2025-12-22T20:04:40.910Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to packages/db/src/schema.ts : Database schema entry point is at `packages/db/src/schema.ts`; migrations emit to `packages/db/drizzle/`

Applied to files:

  • packages/db/src/schema/sessions.ts
📚 Learning: 2025-12-14T14:54:38.009Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:38.009Z
Learning: Applies to packages/db/{src/schema.ts,drizzle/**/*.ts} : Database schema must be defined in `packages/db/src/schema.ts` and migrations must be emitted to `packages/db/drizzle/`

Applied to files:

  • packages/db/src/schema/sessions.ts
📚 Learning: 2025-12-23T18:49:41.966Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.966Z
Learning: Applies to packages/db/src/schema/**/*.{ts,tsx} : Database schema changes must be made in `packages/db/src/schema/` and then `pnpm db:generate` must be run to create migrations

Applied to files:

  • packages/db/src/schema/sessions.ts
🧬 Code graph analysis (5)
packages/lib/src/permissions/enforced-context.ts (3)
packages/lib/src/index.ts (3)
  • ResourceBinding (53-53)
  • EnforcedAuthContext (53-53)
  • SessionClaims (52-52)
apps/processor/src/middleware/auth.ts (1)
  • EnforcedAuthContext (182-182)
packages/lib/src/auth/session-service.ts (1)
  • SessionClaims (8-18)
apps/processor/src/api/avatar.ts (1)
apps/processor/src/middleware/auth.ts (1)
  • hasAuthScope (167-172)
packages/lib/src/permissions/__tests__/enforced-context.test.ts (3)
packages/lib/src/auth/session-service.ts (1)
  • SessionClaims (8-18)
packages/lib/src/index.ts (2)
  • SessionClaims (52-52)
  • EnforcedAuthContext (53-53)
packages/lib/src/permissions/enforced-context.ts (1)
  • EnforcedAuthContext (13-84)
apps/processor/src/api/upload.ts (2)
apps/processor/src/middleware/auth.ts (1)
  • hasAuthScope (167-172)
apps/processor/src/server.ts (1)
  • contentStore (25-25)
apps/processor/src/middleware/auth.ts (3)
packages/lib/src/auth/session-service.ts (1)
  • sessionService (144-144)
packages/lib/src/index.ts (2)
  • sessionService (52-52)
  • EnforcedAuthContext (53-53)
packages/lib/src/permissions/enforced-context.ts (1)
  • EnforcedAuthContext (13-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Tests
🔇 Additional comments (42)
packages/lib/src/services/__tests__/validated-service-token.test.ts (7)

32-38: LGTM! Session service mock correctly configured.

The mock setup properly returns an opaque token with the expected ps_svc_ prefix, aligning with the Phase 2 architecture migration.


61-86: LGTM! Scope filtering tests correctly verify session creation.

The test properly validates that only authorized scopes are passed to sessionService.createSession.


332-357: LGTM! driveId claim propagation correctly tested.

The test ensures driveId is properly passed through to session creation, which is critical for processor validation per the PR objectives.


387-448: LGTM! Convenience functions correctly set resource context.

Tests properly verify that each helper passes the correct resourceId, resourceType, and type-specific identifiers to session creation.


450-475: LGTM! Expiration conversion correctly validated.

The test properly verifies that the string-based expiresIn ('10m') is converted to numeric expiresInMs (600000) for the session service API.


783-815: LGTM! Error bubbling correctly distinguishes error types.

The test properly validates that infrastructure errors from sessionService.createSession bubble up as regular Error instances rather than PermissionDeniedError, enabling callers to differentiate between authorization failures and service failures.


818-841: LGTM! Type guard tests are thorough.

Comprehensive coverage of the isPermissionDeniedError type guard including edge cases for non-Error values and duck-typing scenarios with incorrect error codes.

packages/db/drizzle/0037_slimy_bishop.sql (1)

1-1: LGTM!

The migration correctly adds the nullable drive_id column to the sessions table, aligning with the schema definition in sessions.ts. The nullable column is appropriate since driveId is optional for resource binding.

packages/db/drizzle/meta/_journal.json (1)

263-270: LGTM!

The journal entry is correctly formatted and references the 0037_slimy_bishop migration. The entry follows the established pattern with consistent version and breakpoints settings.

apps/processor/src/api/serve.ts (3)

15-28: LGTM!

The authentication migration from req.serviceAuth to req.auth is correctly implemented. The auth check pattern with null check followed by userId validation is appropriate.


130-143: LGTM!

Consistent auth migration pattern applied to the cached file serving route.


206-219: LGTM!

Consistent auth migration pattern applied to the metadata route.

apps/processor/src/api/avatar.ts (5)

5-5: LGTM!

Import correctly updated from hasServiceScope to hasAuthScope to align with the new EnforcedAuthContext-based authentication system.


45-48: LGTM!

Authentication source correctly migrated to req.auth for the upload endpoint.


64-70: LGTM!

The authorization check correctly uses hasAuthScope with the EnforcedAuthContext. The logic appropriately allows users to modify their own avatar or requires the avatars:write:any scope for modifying another user's avatar.


130-133: LGTM!

Authentication source correctly migrated to req.auth for the delete endpoint.


144-150: LGTM!

Authorization check correctly migrated to use hasAuthScope, maintaining the same security logic for avatar deletion.

packages/db/src/schema/sessions.ts (1)

20-23: The driveId column is correctly added as a nullable text field for resource binding.

The addition is sound—driveId joins resourceType and resourceId as metadata for scoping sessions to specific drives. However, the suggestion about future indexing is not applicable at this time: session queries only filter by tokenHash, userId, expiresAt, and revokedAt. There is no evidence of sessions being queried by driveId in the codebase.

apps/processor/src/api/optimize.ts (1)

14-17: LGTM on auth migration.

The switch from req.serviceAuth to req.auth is consistent across all three routes (/, /batch, /prepare-for-ai). The authentication checks and userId extraction logic remain intact.

Also applies to: 110-113, 193-196

apps/processor/src/middleware/rate-limit.ts (1)

12-26: LGTM on rate-limit auth migration.

The getBucketKey function correctly migrates to req.auth while preserving the rate-limiting semantics:

  • User-based limiting via auth.userId when authenticated
  • IP-based fallback for unauthenticated requests

The comment on line 14 clearly documents the intent.

packages/lib/src/permissions/__tests__/enforced-context.test.ts (2)

49-88: Good scope-checking test coverage.

The hasScope tests comprehensively cover:

  • Exact scope matching
  • Global wildcard (*)
  • Namespace wildcards (files:*)
  • Empty scope arrays

This aligns well with the hasScope implementation logic.


108-141: Resource binding tests cover key authorization scenarios.

Good coverage of isBoundToResource:

  • Unrestricted access when no binding exists (returns true)
  • Exact type+id match
  • Type mismatch rejection
  • ID mismatch rejection
packages/lib/src/permissions/index.ts (1)

21-23: LGTM on public API export.

The re-export of enforced-context makes EnforcedAuthContext and ResourceBinding available through the centralized permissions module, consistent with the existing export patterns and the project's approach to centralized permission logic.

apps/processor/src/types/express.d.ts (1)

1-11: LGTM!

Clean type declaration update. The Express.Request augmentation correctly exposes auth?: EnforcedAuthContext, aligning with the new session-based authentication flow across the processor module.

packages/lib/src/auth/session-service.ts (2)

17-17: LGTM - driveId addition to SessionClaims.

The optional driveId field is correctly added to support processor validation as mentioned in the PR objectives.


107-117: LGTM - validateSession return structure.

The return object correctly maps all session fields including the new driveId, with consistent nullish coalescing for optional properties.

packages/lib/src/permissions/enforced-context.ts (3)

1-50: LGTM - EnforcedAuthContext design.

Excellent security pattern with private constructor ensuring contexts can only be created from validated sessions. The Object.freeze(this) provides immutability guarantees, and ReadonlySet for scopes prevents modification.


52-70: LGTM - Scope matching logic.

The three-tier scope checking (global wildcard → exact match → namespace wildcard) is well-structured and handles the defined ServiceScope patterns correctly.


76-83: LGTM - Resource binding validation.

The semantic of "no binding means unrestricted" is appropriate for service tokens that may operate across resources. The strict matching when a binding exists correctly enforces resource-scoped access.

apps/processor/src/api/upload.ts (4)

85-90: LGTM - Global auth guard middleware.

Clean early return pattern ensuring all upload routes require authentication before proceeding.


104-136: LGTM - Single upload authorization flow.

Comprehensive validation chain: resource binding → driveId → pageId → user authorization. The scope check for files:write:any correctly guards cross-user uploads.


268-302: LGTM - Multiple upload authorization flow.

Correctly mirrors single upload validation with appropriate flexibility for batch uploads where per-file page binding may be optional.


159-164: LGTM - Consistent metadata handling.

The metadata structure using auth.userId as tenantId and 'processor' as service is consistent across all upload paths (dedupe, save, single, and multiple).

packages/lib/src/services/validated-service-token.ts (4)

18-32: LGTM - ServiceScope type definition.

Well-defined union type covering all permission scopes with clear organization (read, write, delete, admin).


34-51: LGTM - Duration parsing helper.

Clean implementation with sensible default fallback. The supported units (s/m/h/d) cover typical token expiration scenarios.


186-200: LGTM - Token creation via sessionService.

Clean migration from JWT to session-based tokens. The createdByService: 'web' attribution provides audit trail clarity.


448-462: LGTM - Upload token creation.

Correctly creates page-scoped service sessions with driveId for processor validation. The UPLOAD_SCOPES constant (['files:write']) provides appropriate minimal permissions.

apps/processor/src/middleware/auth.ts (4)

1-3: LGTM - Import updates.

Clean separation of concerns with sessionService from auth module and EnforcedAuthContext from permissions module.


89-136: LGTM - Authentication middleware.

Clean flow: token extraction → session validation → context construction → optional scope inference. Generic error messages on failure prevent information leakage.


138-165: LGTM - Scope requirement middleware.

Well-structured middleware factory with proper 401/403 distinction. The conditional logging respects test environment configuration.


167-182: LGTM - Helper utilities and re-export.

hasAuthScope provides null-safe scope checking for use in route handlers. requireUserContext offers a clean way to extract authenticated user ID. Re-exporting EnforcedAuthContext maintains backward compatibility for imports from this module.

packages/lib/src/index.ts (1)

52-64: LGTM - Public API surface updates correctly implement new auth architecture.

The exports properly expose the new session-based authentication primitives (sessionService, EnforcedAuthContext) and add ServiceScope type visibility, aligning with the PR's migration from JWT to opaque tokens. The legacy service-auth module exports have been fully removed with no remaining references in the codebase, confirming the migration is complete.

Comment thread apps/processor/src/api/ingest.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f89746e1e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +44 to +48
switch (unit) {
case 's': return value * 1000;
case 'm': return value * 60 * 1000;
case 'h': return value * 60 * 60 * 1000;
case 'd': return value * 24 * 60 * 60 * 1000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reinstate bounds on service token expirations

The new durationToMs just converts the requested duration to milliseconds and then createSession uses it directly for expiresAt, so any caller that passes expiresIn: '0m' will mint an already-expired token (upload/reprocess calls will 401), and any caller that passes a very large duration (e.g. '365d') will create near-permanent service tokens. The previous JWT path capped/validated service token lifetimes; without similar bounds here, a single malformed or user-influenced duration value can break the processor flow or weaken short‑lived token guarantees.

Useful? React with 👍 / 👎.

2witstudios and others added 3 commits January 13, 2026 06:55
Change first auth check from "Authentication required" to
"Service authentication required" for consistent API responses.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restore security bounds that existed in old service-auth.ts:

- Reject zero/negative values (return 5m default)
- Enforce 10s minimum (prevent near-instant expiry)
- Cap at 30 days maximum (prevent near-permanent tokens)
- Log warnings for all invalid/capped durations

Prevents callers from creating already-expired tokens (0m)
or near-permanent service tokens (365d+).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation

- Add validation to reject non-service session types in processor
  auth middleware (service-to-service only)
- Fix sessions-schema test FK violation by improving test isolation:
  - Use cuid for email uniqueness instead of Date.now()
  - Add defensive cleanup of orphaned sessions in beforeEach
  - Delete sessions before users in afterEach
  - Wrap afterEach in try-catch for cascade delete test
  - Reset testUserId at end of afterEach

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/lib/src/services/validated-service-token.ts (1)

172-236: additionalClaims option is accepted but not passed to sessionService.createSession.

The additionalClaims parameter is destructured from options (line 181) but is never used in the createSession call. If this is intentional (session service doesn't support arbitrary claims), consider removing the option from ValidatedTokenOptions to avoid confusion.

🔧 If additionalClaims is no longer supported, remove it from the interface
 export interface ValidatedTokenOptions {
   /** User requesting the token */
   userId: string;
   /** Type of resource being accessed */
   resourceType: ResourceType;
   /** ID of the resource (pageId, driveId, or userId for 'user' type) */
   resourceId: string;
   /** Scopes being requested */
   requestedScopes: ServiceScope[];
   /** Drive ID for drive-scoped tokens (required for processor validation) */
   driveId?: string;
   /** Token expiration (jose duration string, default '5m') */
   expiresIn?: string;
-  /** Additional context for the token */
-  additionalClaims?: Record<string, unknown>;
 }
🧹 Nitpick comments (3)
packages/db/src/__tests__/sessions-schema.test.ts (1)

16-24: Defensive cleanup approach is solid but can be simplified.

The individual delete statements for each known tokenHash work, but they could be consolidated into a single query using inArray for better maintainability.

♻️ Optional consolidation
+import { inArray } from 'drizzle-orm';
+
+const TEST_TOKEN_HASHES = ['abc123hash', 'unique-hash', 'cascade-test', 'hash-1', 'hash-2'];
+
 beforeEach(async () => {
   const uniqueId = createId();

   // Clean any orphaned test sessions (defensive cleanup)
-  await db.delete(sessions).where(eq(sessions.tokenHash, 'abc123hash'));
-  await db.delete(sessions).where(eq(sessions.tokenHash, 'unique-hash'));
-  await db.delete(sessions).where(eq(sessions.tokenHash, 'cascade-test'));
-  await db.delete(sessions).where(eq(sessions.tokenHash, 'hash-1'));
-  await db.delete(sessions).where(eq(sessions.tokenHash, 'hash-2'));
+  await db.delete(sessions).where(inArray(sessions.tokenHash, TEST_TOKEN_HASHES));
apps/processor/src/middleware/auth.ts (2)

137-141: Consider logging the actual error for debugging, not just the message.

The current implementation logs only the error message, which may lose stack trace information useful for debugging authentication issues in production.

♻️ Include full error in logs for debugging
   } catch (error) {
     const message = error instanceof Error ? error.message : 'Invalid token';
-    console.error('Authentication failed:', message);
+    console.error('Authentication failed:', error);
     respondUnauthorized(res, 'Invalid token');
   }

180-186: requireUserContext is a misleading name since it doesn't "require" anything.

The function returns null when auth is missing rather than throwing or enforcing. Consider renaming to getUserId or extractUserId to better reflect its behavior.

♻️ Consider renaming for clarity
-export function requireUserContext(req: Request): string | null {
+export function getUserIdFromAuth(req: Request): string | null {
   const auth = req.auth;
   if (!auth) {
     return null;
   }
   return auth.userId;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f89746 and 38d0ec4.

📒 Files selected for processing (5)
  • apps/processor/src/api/ingest.ts
  • apps/processor/src/middleware/auth.ts
  • packages/db/src/__tests__/sessions-schema.test.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • packages/lib/src/services/validated-service-token.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • packages/db/src/__tests__/sessions-schema.test.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/api/ingest.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: React hook files should use camelCase matching the exported hook name (e.g., useAuth.ts)
Zustand store files should use camelCase with use prefix (e.g., useAuthStore.ts)

Files:

  • packages/db/src/__tests__/sessions-schema.test.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/api/ingest.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • packages/db/src/__tests__/sessions-schema.test.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
  • packages/lib/src/services/__tests__/validated-service-token.test.ts
  • apps/processor/src/api/ingest.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for database queries with PostgreSQL

Files:

  • packages/db/src/__tests__/sessions-schema.test.ts
**/*auth*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*auth*.{ts,tsx}: Use custom JWT authentication with jose library for user management
Use bcryptjs for password hashing

Files:

  • apps/processor/src/middleware/auth.ts
🧬 Code graph analysis (4)
packages/db/src/__tests__/sessions-schema.test.ts (3)
packages/db/src/index.ts (4)
  • db (20-20)
  • sessions (45-45)
  • eq (8-8)
  • users (27-27)
packages/db/src/schema/sessions.ts (1)
  • sessions (6-42)
packages/db/src/schema/auth.ts (1)
  • users (10-33)
apps/processor/src/middleware/auth.ts (3)
packages/lib/src/index.ts (2)
  • sessionService (52-52)
  • EnforcedAuthContext (53-53)
packages/lib/src/auth/session-service.ts (1)
  • sessionService (144-144)
packages/lib/src/permissions/enforced-context.ts (1)
  • EnforcedAuthContext (13-84)
packages/lib/src/services/validated-service-token.ts (3)
packages/lib/src/index.ts (2)
  • ServiceScope (63-63)
  • sessionService (52-52)
packages/lib/src/logging/logger-config.ts (1)
  • loggers (8-18)
packages/lib/src/auth/session-service.ts (1)
  • sessionService (144-144)
packages/lib/src/services/__tests__/validated-service-token.test.ts (3)
packages/lib/src/services/validated-service-token.ts (1)
  • createValidatedServiceToken (172-236)
packages/lib/src/index.ts (1)
  • createValidatedServiceToken (55-55)
packages/lib/src/logging/logger-config.ts (1)
  • loggers (8-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Tests
🔇 Additional comments (12)
packages/db/src/__tests__/sessions-schema.test.ts (2)

26-36: LGTM!

Using createId() for both id and email uniqueness is a solid approach for test isolation. This prevents collisions across parallel test runs.


39-50: LGTM!

The improved teardown handles the cascade delete test case gracefully with try-catch, and resetting testUserId to empty string prevents stale state in subsequent tests.

packages/lib/src/services/validated-service-token.ts (3)

18-32: LGTM!

Well-defined union type for service scopes with clear permission granularity. The scope naming follows a consistent resource:action pattern.


48-86: Thorough bounds validation with good defensive logging.

The implementation correctly handles invalid formats, zero/negative values, minimum enforcement, and maximum capping. The regex pattern ^(\d+)([smhd])$ is appropriate for the expected formats.

One minor observation: the default case on line 70 is unreachable since the regex only allows s|m|h|d, but it serves as a defensive fallback which is fine.


483-492: LGTM!

The upload token creation correctly passes all required session parameters including driveId, resourceType, and resourceId. The createdByService: 'web' provides good audit context.

packages/lib/src/services/__tests__/validated-service-token.test.ts (3)

32-38: LGTM!

The session service mock is correctly set up with mockCreateSession returning an opaque token format (ps_svc_mock-session-token), and the mock is properly wired through vi.mock.


504-617: Excellent test coverage for duration bounds validation.

The tests thoroughly cover:

  • Zero duration → defaults to 5m
  • Invalid format → defaults to 5m
  • Excessive duration (365d) → caps at 30d
  • Below minimum (1s) → floors to 10s
  • Valid duration (5m) → no warnings

This matches the implementation in durationToMs() perfectly.


873-896: LGTM!

The default expiration tests correctly verify that upload tokens default to 10m (600000ms) while custom expirations (15m → 900000ms) are properly converted.

apps/processor/src/middleware/auth.ts (3)

2-3: LGTM!

Clean imports from the centralized auth and permissions packages, aligning with the project's module organization.


103-119: Solid session validation flow with proper type enforcement.

The middleware correctly:

  1. Validates the session token via sessionService.validateSession
  2. Rejects non-service session types (line 112) - important for service-to-service isolation
  3. Builds an immutable EnforcedAuthContext from validated claims

This enforces that the processor only accepts opaque service tokens (ps_svc_*), as intended by the PR.


173-178: LGTM!

Simple, well-typed helper that safely delegates to EnforcedAuthContext.hasScope(). The undefined check is necessary since req.auth is optional.

apps/processor/src/api/ingest.ts (1)

13-20: LGTM!

The authentication migration from req.serviceAuth to req.auth is correctly implemented. The dual check (auth exists + userId exists) provides good defense-in-depth, though EnforcedAuthContext should always have a userId since it's a required field in fromSession.

2witstudios and others added 2 commits January 13, 2026 09:05
- Remove destructive `db.delete(users)` that deleted ALL users
- Add targeted afterEach cleanup for test-specific users only
- Wrap cleanup in try-catch for cascade delete edge cases
- Fixes FK violation caused by cross-package database interference

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…eview cleanup

Security fix:
- Add createSignedBroadcastHeaders to notification broadcasts (was rejected with 401)

Code review cleanup:
- Remove unused additionalClaims from ValidatedTokenOptions
- Simplify test cleanup using inArray instead of multiple deletes
- Log full error object in auth middleware for better debugging
- Rename requireUserContext to getUserId (reflects getter behavior)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/lib/src/notifications/notifications.ts (1)

20-27: Consider logging non-2xx responses for debugging.

The broadcast is fire-and-forget, which is appropriate. However, silently ignoring non-2xx responses (e.g., 401, 500) can hide issues. Adding a status check would help catch regressions after this HMAC fix.

♻️ Optional: Log failed broadcast responses
-    await fetch(`${realtimeUrl}/api/broadcast`, {
+    const response = await fetch(`${realtimeUrl}/api/broadcast`, {
       method: 'POST',
       headers: createSignedBroadcastHeaders(requestBody),
       body: requestBody,
     });
+    if (!response.ok) {
+      console.warn(`Broadcast failed with status ${response.status}`);
+    }
   } catch (error) {
     console.error('Failed to broadcast notification:', error);
   }
apps/processor/src/middleware/auth.ts (1)

12-12: Avoid any type cast.

Express Request extends Node's IncomingMessage, which already has a url?: string property. The cast is unnecessary and violates the coding guideline to never use any types.

♻️ Suggested fix
-    (req as any).url,
+    req.url,
packages/lib/src/services/validated-service-token.ts (1)

150-156: Outdated comment: tokens are now opaque, not JWT.

The comment on line 152 still says "The signed JWT token" but after this migration, tokens are opaque session tokens (ps_svc_*). Consider updating the comment for accuracy.

📝 Suggested comment update
 /**
  * Result of a validated token creation
  */
 export interface ValidatedTokenResult {
-  /** The signed JWT token */
+  /** The opaque session token */
   token: string;
   /** Scopes that were actually granted (may be subset of requested) */
   grantedScopes: ServiceScope[];
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55ddfce and bface73.

📒 Files selected for processing (4)
  • apps/processor/src/middleware/auth.ts
  • packages/db/src/__tests__/sessions-schema.test.ts
  • packages/lib/src/notifications/notifications.ts
  • packages/lib/src/services/validated-service-token.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/db/src/tests/sessions-schema.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • packages/lib/src/notifications/notifications.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: React hook files should use camelCase matching the exported hook name (e.g., useAuth.ts)
Zustand store files should use camelCase with use prefix (e.g., useAuthStore.ts)

Files:

  • packages/lib/src/notifications/notifications.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • packages/lib/src/notifications/notifications.ts
  • apps/processor/src/middleware/auth.ts
  • packages/lib/src/services/validated-service-token.ts
**/*auth*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*auth*.{ts,tsx}: Use custom JWT authentication with jose library for user management
Use bcryptjs for password hashing

Files:

  • apps/processor/src/middleware/auth.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to **/*auth*.{ts,tsx} : Use custom JWT authentication with jose library for user management
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to **/*.{ts,tsx} : Always use the Drizzle client and database exports from `pagespace/db` (e.g., `import { db, pages } from 'pagespace/db'`) for all database access

Applied to files:

  • packages/lib/src/notifications/notifications.ts
📚 Learning: 2025-12-23T18:49:41.966Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.966Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : For database access, always use Drizzle client from `pagespace/db`: `import { db, pages } from 'pagespace/db';`

Applied to files:

  • packages/lib/src/notifications/notifications.ts
📚 Learning: 2025-12-14T14:54:38.009Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:38.009Z
Learning: Applies to apps/**/*.ts : Always use Drizzle client and queries from `pagespace/db` for database access instead of direct queries

Applied to files:

  • packages/lib/src/notifications/notifications.ts
📚 Learning: 2025-12-22T20:04:40.910Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to **/*.{ts,tsx} : Always use Drizzle client from `pagespace/db` package for database access

Applied to files:

  • packages/lib/src/notifications/notifications.ts
📚 Learning: 2025-12-22T20:04:40.910Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to packages/db/**/*.{ts,tsx} : Use Drizzle ORM for database queries with PostgreSQL

Applied to files:

  • packages/lib/src/notifications/notifications.ts
📚 Learning: 2025-12-23T18:49:41.966Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.966Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : Use centralized Drizzle ORM with PostgreSQL for all database operations - no direct SQL or other ORMs

Applied to files:

  • packages/lib/src/notifications/notifications.ts
🧬 Code graph analysis (1)
packages/lib/src/notifications/notifications.ts (1)
apps/web/src/lib/auth/auth-fetch.ts (1)
  • fetch (44-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Tests
🔇 Additional comments (13)
packages/lib/src/notifications/notifications.ts (2)

4-4: LGTM on the import.

The addition of createSignedBroadcastHeaders aligns with the security fix to add HMAC signatures for broadcast authentication.


14-24: The code is correct. createSignedBroadcastHeaders returns both Content-Type: application/json and the signature header, so no changes are needed.

Likely an incorrect or invalid review comment.

apps/processor/src/middleware/auth.ts (6)

1-3: LGTM!

The imports align with the new session-based authentication architecture, bringing in sessionService for validation and EnforcedAuthContext from the centralized permissions module.


27-76: LGTM!

The scope inference logic correctly maps request URLs to the appropriate scopes. The separate loops provide clear, readable code for each scope category.


89-141: LGTM!

The middleware correctly implements the new session-based authentication flow:

  1. Validates the opaque token via sessionService.validateSession
  2. Rejects non-service sessions with 403 (proper distinction from 401)
  3. Constructs the immutable EnforcedAuthContext and attaches it to req.auth
  4. Performs scope inference with appropriate logging for debugging

The generic error messages in the catch block appropriately avoid leaking sensitive validation details.


143-170: LGTM!

The middleware factory correctly leverages EnforcedAuthContext.hasScope for authorization checks and provides informative error messages including the missing scope.


172-185: LGTM!

Both helper functions properly handle the case where req.auth may be undefined (e.g., when AUTH_REQUIRED=false), returning safe fallback values.


187-187: LGTM!

Re-exporting EnforcedAuthContext provides a convenient single import point for consumers of this middleware module.

packages/lib/src/services/validated-service-token.ts (5)

18-32: Well-defined scope type with exhaustive literals.

Good use of a string literal union type for ServiceScope. This provides compile-time safety when checking scopes and aligns with the TypeScript coding guidelines.


34-37: LGTM!

Constants follow UPPER_SNAKE_CASE convention and have clear inline comments. The bounds (10s min, 30 days max) are reasonable for service tokens.


48-86: Solid duration parsing with proper bounds validation.

The function handles edge cases well: invalid formats, zero/negative values, and enforces min/max bounds with logging. The regex pattern correctly constrains valid inputs.

Minor note: The default case in the switch (line 70) is unreachable since the regex already ensures unit is one of [smhd], but it's acceptable as defensive coding.


480-489: LGTM!

Consistent with createValidatedServiceToken. The driveId is required here (per UploadTokenOptions), which correctly supports the processor's drive-based validation for uploads.


218-227: Clean migration to session-based token creation.

The payload structure is well-organized. The as string[] cast on line 221 is safe since ServiceScope is a union of string literals. The sessionService.createSession method accepts all the fields being passed, including the optional driveId and createdByService parameters.

2witstudios and others added 2 commits January 13, 2026 10:22
Use separate ID tracking variables with individual try/catch blocks
to prevent cleanup failures from affecting subsequent tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Match the pattern from permissions.test.ts - clean ALL users at the
start of beforeEach instead of selective cleanup in afterEach.
This ensures a clean database state even if previous tests crashed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/lib/src/__tests__/notifications.test.ts (1)

1-11: Unused afterEach import.

The afterEach import on line 1 is no longer used after the cleanup logic was moved into beforeEach. Consider removing it to keep imports clean.

The import cleanup on line 10 (removing sql and notifications) looks good—only importing what's needed.

Suggested fix
-import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 459f03b and a0b8a18.

📒 Files selected for processing (1)
  • packages/lib/src/__tests__/notifications.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • packages/lib/src/__tests__/notifications.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: React hook files should use camelCase matching the exported hook name (e.g., useAuth.ts)
Zustand store files should use camelCase with use prefix (e.g., useAuthStore.ts)

Files:

  • packages/lib/src/__tests__/notifications.test.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • packages/lib/src/__tests__/notifications.test.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T20:04:40.910Z
Learning: Applies to **/*auth*.{ts,tsx} : Use custom JWT authentication with jose library for user management
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to **/*.{ts,tsx} : Always use the Drizzle client and database exports from `pagespace/db` (e.g., `import { db, pages } from 'pagespace/db'`) for all database access

Applied to files:

  • packages/lib/src/__tests__/notifications.test.ts
📚 Learning: 2025-12-14T14:54:38.009Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:38.009Z
Learning: Applies to apps/**/*.ts : Always use Drizzle client and queries from `pagespace/db` for database access instead of direct queries

Applied to files:

  • packages/lib/src/__tests__/notifications.test.ts
📚 Learning: 2025-12-23T18:49:41.966Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.966Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : For database access, always use Drizzle client from `pagespace/db`: `import { db, pages } from 'pagespace/db';`

Applied to files:

  • packages/lib/src/__tests__/notifications.test.ts
🧬 Code graph analysis (1)
packages/lib/src/__tests__/notifications.test.ts (1)
packages/db/src/index.ts (1)
  • db (20-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint & TypeScript Check
  • GitHub Check: Unit Tests
🔇 Additional comments (1)
packages/lib/src/__tests__/notifications.test.ts (1)

22-36: LGTM!

The refactored test setup is clean and well-documented. Moving cleanup to the start of beforeEach (delete-then-create) ensures a fresh state for each test, and the comments clearly explain the cascade deletion behavior. The mock reset placement before test data is appropriate.

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