Skip to content

fix(user-profiles): require auth + ownership on profile read/write - #1022

Open
mmcintosh wants to merge 3 commits into
mainfrom
fix/user-profiles-auth
Open

fix(user-profiles): require auth + ownership on profile read/write#1022
mmcintosh wants to merge 3 commits into
mainfrom
fix/user-profiles-auth

Conversation

@mmcintosh

Copy link
Copy Markdown
Collaborator

Description

GET/PUT /api/user-profiles/:userId (in user-profiles/index.ts) performed no authentication — any unauthenticated caller could read or overwrite any user's custom profile data by id (information disclosure + data tampering). Only GET /schema was intentionally public.

This gates both :userId routes with requireAuth() + an ownership check, and — since it touches these handlers — types the sub-app instead of casting.

Fixes #1021

Changes

  • Auth: add requireAuth() to GET/PUT /api/user-profiles/:userId.
  • Ownership: add canAccessProfile(c, userId) — a signed-in user may act on their own profile; admins (user.role === 'admin') on any; else 403 Forbidden. GET /schema unchanged (public field definitions).
  • Types (same routes): new Hono<{ Bindings; Variables }>() (same pattern as the analytics/email-plugin routes) replaces the (c.env as any).DB || (c as any).db casts with the typed c.env.DB — the c.db fallback was dead (nothing sets it). canAccessProfile is a targetUserId is string type-guard whose predicate also narrows userId, which clears the two pre-existing string | undefined errors on getCustomData/saveCustomData. Net for the file: 0 any, 0 tsc errors.

One file, +33/−5.

Not in this PR: global-variables has the identical unauthenticated-CRUD pattern (see linked issue). A separate issue + PR follows for it.

Testing

Type-checked against the repo toolchain (tsc --noEmit, scoped to the changed file — clean). No unit/E2E tests added yet — opened as a draft for discussion of the auth model; happy to add a route test asserting 401 (unauthenticated) / 403 (cross-user) / 200 (owner) if preferred.

Unit Tests

  • Added/updated unit tests
  • All unit tests passing

E2E Tests

  • Added/updated E2E tests
  • All E2E tests passing

Screenshots/Videos

N/A — API-only change.

Checklist

  • Code follows project conventions
  • Tests added/updated and passing
  • Type checking passes
  • No console errors or warnings
  • Documentation updated (if needed)

GET/PUT /api/user-profiles/:userId performed no auth — any unauthenticated
caller could read or overwrite any user's custom profile data by id
(information disclosure + data tampering). Gate both routes with requireAuth()
plus an ownership check: a signed-in user may act on their own profile, admins
on any, else 403. GET /schema stays public (field definitions only).

While securing these routes, type the Hono sub-app with Bindings/Variables
(matching the analytics/email plugins) instead of the `(c.env as any).DB ||
(c as any).db` casts, and make canAccessProfile a `targetUserId is string`
type-guard. The guard predicate also narrows userId, clearing the two
pre-existing string|undefined errors on getCustomData/saveCustomData.
Net for the file: 0 `any`, 0 tsc errors.
…canAccessProfile

requireAuth() guarantees the user is present before canAccessProfile runs.
A null user here means middleware was bypassed — throw loudly instead of
masking the bug with a silent 403 Forbidden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Ctx in test env

Module-level caches in plugin-middleware, document-scalar-schema, and the
Hono executionCtx getter (throws without a real Cloudflare execution context)
caused 25 test failures in CI. Fix by:

- Export resetScalarSchemaCaches() from document-scalar-schema.ts; call it
  in createTestD1().close() and in migrations-d45.test.ts beforeEach so each
  fresh in-memory DB gets a clean cache slate.
- Call invalidatePluginStatusCache() in beforeEach of the three plugin-middleware
  describe blocks so mock DB lookups aren't shadowed by prior-test cache entries.
- Add safeExecCtx() helper in api.ts that wraps c.executionCtx in try-catch and
  returns null when no ExecutionContext is set (test environment). scheduleKvWrite
  updated to accept null/undefined ctx and exit early — keeps KV scheduling a
  no-op in tests without changing production behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lane711
lane711 deployed to internal July 28, 2026 19:39 — with GitHub Actions Active
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.

Security: user-profiles API allows unauthenticated read/write of any user's profile data

2 participants