Skip to content

feat: port autumn, gitlab, posthog, spotify, workos, x emulators from UsefulSoftwareCo fork#18

Merged
amondnet merged 6 commits into
mainfrom
amondnet/web
Jul 4, 2026
Merged

feat: port autumn, gitlab, posthog, spotify, workos, x emulators from UsefulSoftwareCo fork#18
amondnet merged 6 commits into
mainfrom
amondnet/web

Conversation

@amondnet

@amondnet amondnet commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports six service emulators (autumn, gitlab, posthog, spotify, workos, x) from the UsefulSoftwareCo/emulate fork into this repo. These emulators are present in that fork but absent from the vercel-labs upstream.

⚠️ Action required before merge/release: npm Trusted Publisher must be configured on npmjs.com for the six new packages (@pleaseai/emulate-autumn, @pleaseai/emulate-gitlab, @pleaseai/emulate-posthog, @pleaseai/emulate-spotify, @pleaseai/emulate-workos, @pleaseai/emulate-x) before the release-please release workflow can actually publish them to npm.

Changes

  • New packages: packages/<service> named @pleaseai/emulate-<service> for autumn, gitlab, posthog, spotify, workos, x, all built against published @emulators/core ^0.6.0
  • Removed fork-only APIs: ServiceManifest/manifest.ts, /_emulate fault injection
  • Converted tests from vitest to bun:test
  • Deconflicted test ports across the new packages
  • Fixed autumn balances.track response to match the autumn-js TrackResponse schema (autumn-js pinned to 1.2.28)
  • Registered the six new emulators in the CLI registry (ports 4007–4012)
  • Added release-please config/manifest entries for the new packages
  • Updated root README, added per-package READMEs and skills/<service>/SKILL.md files

Excluded from this port

  • cloudflare — Durable Object hosting infra, not an emulator
  • mcp — depends on fork-only @emulators/github and core LedgerEntry

Test Plan

  • bun run lint — all green
  • bun run build — all green
  • bun run type-check — all green
  • bun run test — all green (27 turbo tasks, 14 test suites, 0 failures)

Related Issues

N/A


Summary by cubic

Ports six new service emulators—Autumn, GitLab, PostHog, Spotify, WorkOS, and X—into this repo as @pleaseai/emulate-<service> packages on @emulators/core ^0.6.0, registered in the CLI and release-please. Action required: configure npm Trusted Publisher for the six new packages before publishing.

  • New Features

    • @pleaseai/emulate-autumn: RPC API (customers, balances, plans, billing) + hosted checkout; balances.track aligns with autumn-js TrackResponse.
    • @pleaseai/emulate-gitlab: POST /api/graphql with full schema introspection and query validation.
    • @pleaseai/emulate-posthog: event capture; projects/events/users API; OAuth 2.0 with dynamic client registration; discovery; /openapi.json.
    • @pleaseai/emulate-spotify: client-credentials token; catalog (search, artists, albums, tracks); /openapi.json.
    • @pleaseai/emulate-workos: User Management, organizations, API keys, OAuth/OIDC (JWKS, discovery, token exchange), Vault KV; compatible with @workos-inc/node.
    • @pleaseai/emulate-x: OAuth 2.0 PKCE (authorize/token/revoke), tweets, users; /2/openapi.json.
    • CLI registry updated (ports 4007–4012); packages added to release-please, README, and per-package docs.
  • Bug Fixes

    • Security and fidelity hardening:
      • WorkOS: validate registered client_id and redirect_uri on authorize; bind code/refresh redemption to issuing client_id; validate redirect_uri on token exchange; enforce S256 PKCE when present; stricter org name validation.
      • X: require redirect_uri on authorize; revalidate client/redirect_uri/scope at consent; revalidate code_challenge_method=S256 on direct consent; derive client type from secret presence; decrement author tweet_count on delete.
      • PostHog: strict integer project ids; enforce client_secret_post at token endpoint for confidential clients.
      • Spotify: return existing credentials for duplicate emulator apps; expire bearer tokens per expires_in; require q and type on /v1/search; ignore Basic auth headers without a : separator.
      • Autumn: settle only completed checkout sessions in the webhook stand-in; preserve trial semantics on immediate no-card attaches.
    • Replaced Math.random with node:crypto in WorkOS id/token/jti generation and X snowflake id generator (Sonar typescript:S2245).
    • Expanded tests across X tweets/users/oauth, PostHog private API and event capture, WorkOS organizations, Autumn RPC and checkout settle flow, Spotify default seed.

Written for commit 672c5be. Summary will update on new commits.

… UsefulSoftwareCo fork

Bring the six service emulators that exist in the UsefulSoftwareCo/emulate
fork but not in the vercel-labs upstream, adapted to this repo's conventions:

- rename to @pleaseai/emulate-<service>, depend on published @emulators/core ^0.6.0
- drop fork-only ServiceManifest exports and the /_emulate fault-injection test
  (both rely on the fork's unpublished core 0.13 APIs)
- convert vitest tests to bun:test, deconflict test ports for parallel turbo runs
- include value/balance in autumn balances.track response per autumn-js TrackResponse
- register all six services in the emulate CLI registry, release-please, README,
  and add per-package READMEs and per-service skills

The fork's cloudflare (Durable Object hosting infra) and mcp (depends on
fork-only @emulators/github and core LedgerEntry) packages are intentionally
not ported.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@amondnet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d15eedc8-9c99-4ab5-bf17-b4a825f3727f

📥 Commits

Reviewing files that changed from the base of the PR and between 838efed and 86be056.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (86)
  • .gitignore
  • .release-please-manifest.json
  • README.md
  • packages/autumn/README.md
  • packages/autumn/package.json
  • packages/autumn/src/__tests__/autumn-trial.test.ts
  • packages/autumn/src/__tests__/autumn.test.ts
  • packages/autumn/src/entities.ts
  • packages/autumn/src/index.ts
  • packages/autumn/src/routes/api.ts
  • packages/autumn/src/routes/checkout.ts
  • packages/autumn/src/routes/openapi.ts
  • packages/autumn/src/serialize.ts
  • packages/autumn/src/store.ts
  • packages/autumn/tsconfig.json
  • packages/autumn/tsup.config.ts
  • packages/emulate/package.json
  • packages/emulate/src/registry.ts
  • packages/gitlab/README.md
  • packages/gitlab/package.json
  • packages/gitlab/src/__tests__/gitlab.test.ts
  • packages/gitlab/src/index.ts
  • packages/gitlab/src/routes/graphql.ts
  • packages/gitlab/src/schema-sdl.ts
  • packages/gitlab/src/schema.ts
  • packages/gitlab/tsconfig.json
  • packages/gitlab/tsup.config.ts
  • packages/posthog/README.md
  • packages/posthog/package.json
  • packages/posthog/src/__tests__/posthog.test.ts
  • packages/posthog/src/entities.ts
  • packages/posthog/src/index.ts
  • packages/posthog/src/routes/api.ts
  • packages/posthog/src/routes/oauth.ts
  • packages/posthog/src/routes/openapi.ts
  • packages/posthog/src/store.ts
  • packages/posthog/tsconfig.json
  • packages/posthog/tsup.config.ts
  • packages/spotify/README.md
  • packages/spotify/package.json
  • packages/spotify/src/__tests__/spotify.test.ts
  • packages/spotify/src/entities.ts
  • packages/spotify/src/index.ts
  • packages/spotify/src/routes/catalog.ts
  • packages/spotify/src/routes/openapi.ts
  • packages/spotify/src/routes/token.ts
  • packages/spotify/src/store.ts
  • packages/spotify/tsconfig.json
  • packages/spotify/tsup.config.ts
  • packages/workos/README.md
  • packages/workos/package.json
  • packages/workos/src/__tests__/workos.test.ts
  • packages/workos/src/entities.ts
  • packages/workos/src/helpers.ts
  • packages/workos/src/index.ts
  • packages/workos/src/keys.ts
  • packages/workos/src/routes/api-keys.ts
  • packages/workos/src/routes/oauth.ts
  • packages/workos/src/routes/openapi.ts
  • packages/workos/src/routes/organizations.ts
  • packages/workos/src/routes/user-management.ts
  • packages/workos/src/routes/vault.ts
  • packages/workos/src/store.ts
  • packages/workos/tsconfig.json
  • packages/workos/tsup.config.ts
  • packages/x/README.md
  • packages/x/package.json
  • packages/x/src/__tests__/x.test.ts
  • packages/x/src/entities.ts
  • packages/x/src/index.ts
  • packages/x/src/routes/auth.ts
  • packages/x/src/routes/oauth.ts
  • packages/x/src/routes/openapi.ts
  • packages/x/src/routes/tweets.ts
  • packages/x/src/routes/users.ts
  • packages/x/src/store.ts
  • packages/x/tsconfig.json
  • packages/x/tsup.config.ts
  • release-please-config.json
  • skills/autumn/SKILL.md
  • skills/gitlab/SKILL.md
  • skills/posthog/SKILL.md
  • skills/spotify/SKILL.md
  • skills/workos/SKILL.md
  • skills/x/SKILL.md
  • sonar-project.properties
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch amondnet/web

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.

@codacy-production

codacy-production Bot commented Jul 4, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 critical · 49 high · 6 medium

Alerts:
⚠ 57 issues (≤ 0 issues of at least minor severity)

Results:
57 new issues

Category Results
BestPractice 11 high
ErrorProne 6 medium
28 high
Security 2 critical
10 high

View in Codacy

🟢 Metrics 1279 complexity · 113 duplication

Metric Results
Complexity 1279
Duplication 113

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@socket-security

socket-security Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedautumn-js@​1.2.28991008298100
Added@​workos-inc/​node@​8.13.099100100100100

View full report

Comment thread packages/workos/src/helpers.ts Fixed
Comment thread packages/workos/src/helpers.ts Fixed
Comment thread packages/workos/src/helpers.ts Fixed
Comment thread packages/workos/src/keys.ts Fixed
Comment thread packages/workos/src/keys.ts Fixed
Comment thread packages/x/src/store.ts Fixed
- replace Math.random with node:crypto in workos id/token/jti generation and
  the x snowflake id generator (typescript:S2245)
- exclude hand-authored static OpenAPI spec files from sonar coverage
- broaden tests: x tweets/users/oauth surfaces, posthog private API and event
  capture, workos organizations REST, autumn RPC list/update/portal and hosted
  checkout settle flow, spotify default seed — new-code line coverage 84.6%
@amondnet
amondnet marked this pull request as ready for review July 4, 2026 04:04
@amondnet

amondnet commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@cubic-dev-ai cubic-dev-ai 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.

20 issues found across 87 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/autumn/src/serialize.ts">

<violation number="1" location="packages/autumn/src/serialize.ts:47">
P2: Subscriptions can be marked `trialing` when the trial is not card-required, which makes status/eligibility diverge from the documented flow. This happens because `activateSubscription` ignores `free_trial.card_required`; gating trial status on that flag would keep non-card-required plans active as intended.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Dev as Developer
    participant CLI as CLI (registry)
    participant Core as @emulators/core
    participant Store as Store (collections)
    participant Plugin as WorkOS Plugin
    participant Auth as authMiddleware
    participant SDK as @workos-inc/node

    Note over Dev,SDK: NEW: WorkOS emulator runtime flow

    Dev->>CLI: npx @pleaseai/emulate --service workos -p 4011
    CLI->>Core: createServer(workosPlugin, {port, baseUrl, fallbackUser})
    Core->>Plugin: register(app, store, webhooks, baseUrl, tokenMap)
    Plugin->>Core: register routes (oauth, user-management, organizations, vault, openapi)
    Core-->>CLI: HTTP server listening on port 4011

    Note over Dev,SDK: Developer uses real @workos-inc/node SDK

    SDK->>Core: POST /user_management/authorize?client_id=...&redirect_uri=...
    Core->>Auth: authMiddleware (validates Bearer sk_...)
    Auth-->>Core: authUser set (if valid)
    alt Valid API key
        Core->>Plugin: route handler
        Plugin->>Store: get users collection
        Store-->>Plugin: list of seeded users
        Plugin-->>Core: HTML authorize page (user buttons)
        Core-->>SDK: 200 HTML
    else Invalid API key
        Core-->>SDK: 401 Unauthorized
    end

    Note over SDK,Plugin: User selects identity → authorization code issued

    SDK->>Core: POST /user_management/authorize/submit (email, client_id, redirect_uri)
    Core->>Plugin: handler
    Plugin->>Store: ensureUserByEmail (create if new)
    Plugin->>Store: authCodes.insert (new code)
    Store-->>Plugin: code entity
    Plugin-->>Core: 302 redirect to redirect_uri?code=...
    Core-->>SDK: 302 redirect

    SDK->>Core: POST /user_management/authenticate (grant_type=authorization_code, code, client_id)
    Core->>Plugin: handler
    Plugin->>Store: authCodes.findOneBy('code', code)
    alt Code valid & unused
        Store-->>Plugin: auth code record
        Plugin->>Store: authCodes.update (used=true)
        Plugin->>Plugin: generate access_token (sign JWT with jose)
        Plugin->>Store: sessions.insert (new session with refresh_token)
        Store-->>Plugin: session
        Plugin-->>Core: 200 JSON { user, access_token, refresh_token }
        Core-->>SDK: authentication response
    else Code used or invalid
        Store-->>Plugin: null or already used
        Plugin-->>Core: 400 invalid_grant
        Core-->>SDK: error
    end

    Note over SDK,Plugin: Session refresh with org switch

    SDK->>Core: POST /user_management/authenticate (grant_type=refresh_token, refresh_token, organization_id)
    Core->>Plugin: handler
    Plugin->>Store: sessions.findOneBy('refresh_token', token)
    alt Session valid & not revoked
        Store-->>Plugin: session
        Plugin->>Store: verify membership for requested org
        alt User has active membership
            Store-->>Plugin: membership
            Plugin->>Store: sessions.update (revoke old)
            Plugin->>Store: new sessions.insert
            Plugin-->>Core: 200 with new tokens
            Core-->>SDK: refreshed session
        else No membership
            Plugin-->>Core: 403 sso_required
            Core-->>SDK: error
        end
    else Session revoked
        Plugin-->>Core: 400 invalid_grant
        Core-->>SDK: error
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/workos/src/routes/user-management.ts Outdated
Comment thread packages/workos/src/routes/user-management.ts Outdated
Comment thread packages/x/src/routes/oauth.ts
Comment thread packages/workos/src/routes/oauth.ts
Comment thread packages/workos/src/routes/oauth.ts
Comment thread packages/workos/src/routes/organizations.ts Outdated
Comment thread packages/x/src/routes/oauth.ts Outdated
Comment thread packages/workos/src/routes/oauth.ts
Comment thread packages/autumn/src/serialize.ts
Comment thread packages/x/src/routes/openapi.ts
@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR ports six service emulators — Autumn, GitLab, PostHog, Spotify, WorkOS, and X — from the UsefulSoftwareCo fork, registering them in the CLI at ports 4007–4012 and wiring them into release-please. The PR also back-ports several security-hardening fixes (PKCE enforcement, redirect_uri binding, client-secret validation, crypto-safe ID generation) that were already noted in previous review rounds.

  • Most emulators are well-implemented: the X, PostHog, Spotify, and Autumn emulators have thorough OAuth validation including PKCE S256 enforcement, constant-time secret comparison, single-use code deletion, and registered-client checks — and all three previously-flagged issues (WorkOS PKCE/redirect_uri at token exchange, WorkOS authorize client validation, Spotify Basic-auth separator guard) are addressed in the current commit.
  • WorkOS /user_management/authorize validation gap: the AuthKit-path authorize endpoints (GET /user_management/authorize, POST /user_management/authorize/submit) accept any client_id and redirect_uri without checking the client registry, unlike the /oauth2/authorize path which uses validateAuthorizeRequest; a misconfigured test silently receives a valid code.
  • GitLab emulator provides full schema introspection and query validation for POST /api/graphql; Autumn checkout correctly mirrors Stripe's async webhook timing.

Confidence Score: 4/5

Safe to merge with one fix: the WorkOS AuthKit authorize path should validate registered clients and redirect URIs before minting codes.

The AuthKit-path /user_management/authorize endpoints accept any client_id and redirect_uri without checking the client registry. The identical gap was already caught and fixed on the /oauth2/authorize path in this same PR, but the AuthKit path was not updated consistently. All other OAuth flows across the six new emulators are correctly hardened.

packages/workos/src/routes/user-management.ts — issueCodeRedirect and the authorize/submit handler need client-registry validation before minting authorization codes.

Important Files Changed

Filename Overview
packages/workos/src/routes/user-management.ts AuthKit-path /user_management/authorize and its submit handler accept any client_id and redirect_uri without checking the client registry, unlike /oauth2/authorize which has validateAuthorizeRequest; a misconfigured client_id silently produces a valid code.
packages/workos/src/routes/oauth.ts Authorization-code flow: client validation, PKCE S256 enforcement, redirect_uri binding, and client-binding on refresh_token are all present and correct; all issues from previous review threads are addressed.
packages/posthog/src/routes/oauth.ts PKCE (S256), client validation, redirect_uri matching, and client_secret enforcement for confidential clients are all correct; expired codes still accumulate in the Map (pre-existing thread).
packages/x/src/routes/oauth.ts Thorough OAuth 2.0 PKCE, client registration validation, redirect_uri matching, and consent-form re-validation; confidential/public client auth method enforcement is correct.
packages/spotify/src/routes/token.ts Basic-auth header parsing now guards sep >= 0 before slicing (previously flagged issue is fixed); client_credentials grant and constant-time secret comparison are correct.
packages/autumn/src/routes/api.ts RPC routes (customers, balances, plans, billing) are correctly shaped; balances.track returns the autumn-js TrackResponse schema; free trial semantics preserved on immediate no-card attach.
packages/autumn/src/routes/checkout.ts Checkout settle flow correctly mirrors Stripe async webhook semantics; bulk /checkout/settle filters for completed status; per-session /checkout/:id/settle is an intentional explicit override.
packages/gitlab/src/routes/graphql.ts Minimal GraphQL emulator with schema introspection and query validation; field selection and inline fragments handled; schema-sdl defines a minimal but valid GitLab schema.
packages/emulate/src/registry.ts Six new emulators registered at ports 4007-4012 with no port conflicts against existing registrations.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant WorkOS_Emulator

    Note over Client,WorkOS_Emulator: OAuth path (/oauth2/authorize) — validated ✅
    Client->>WorkOS_Emulator: "GET /oauth2/authorize?client_id=X&redirect_uri=Y"
    WorkOS_Emulator->>WorkOS_Emulator: "validateAuthorizeRequest()<br/>• client exists?<br/>• redirect_uri registered?"
    alt invalid client or redirect_uri
        WorkOS_Emulator-->>Client: 401/422 error
    else valid
        WorkOS_Emulator-->>Client: "302 → redirect_uri?code=..."
    end

    Note over Client,WorkOS_Emulator: AuthKit path (/user_management/authorize) — NOT validated ⚠️
    Client->>WorkOS_Emulator: "GET /user_management/authorize?client_id=X&redirect_uri=Y"
    WorkOS_Emulator->>WorkOS_Emulator: "issueCodeRedirect()<br/>• only checks redirect_uri present<br/>• no client registry lookup"
    WorkOS_Emulator-->>Client: "302 → redirect_uri?code=... (any client_id accepted)"

    Note over Client,WorkOS_Emulator: Token exchange (both paths check client binding ✅)
    Client->>WorkOS_Emulator: "POST /user_management/authenticate<br/>grant_type=authorization_code&code=...&client_id=X"
    WorkOS_Emulator->>WorkOS_Emulator: "authCode.client_id === client_id?"
    WorkOS_Emulator-->>Client: access_token + refresh_token
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant WorkOS_Emulator

    Note over Client,WorkOS_Emulator: OAuth path (/oauth2/authorize) — validated ✅
    Client->>WorkOS_Emulator: "GET /oauth2/authorize?client_id=X&redirect_uri=Y"
    WorkOS_Emulator->>WorkOS_Emulator: "validateAuthorizeRequest()<br/>• client exists?<br/>• redirect_uri registered?"
    alt invalid client or redirect_uri
        WorkOS_Emulator-->>Client: 401/422 error
    else valid
        WorkOS_Emulator-->>Client: "302 → redirect_uri?code=..."
    end

    Note over Client,WorkOS_Emulator: AuthKit path (/user_management/authorize) — NOT validated ⚠️
    Client->>WorkOS_Emulator: "GET /user_management/authorize?client_id=X&redirect_uri=Y"
    WorkOS_Emulator->>WorkOS_Emulator: "issueCodeRedirect()<br/>• only checks redirect_uri present<br/>• no client registry lookup"
    WorkOS_Emulator-->>Client: "302 → redirect_uri?code=... (any client_id accepted)"

    Note over Client,WorkOS_Emulator: Token exchange (both paths check client binding ✅)
    Client->>WorkOS_Emulator: "POST /user_management/authenticate<br/>grant_type=authorization_code&code=...&client_id=X"
    WorkOS_Emulator->>WorkOS_Emulator: "authCode.client_id === client_id?"
    WorkOS_Emulator-->>Client: access_token + refresh_token
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/workos/src/routes/user-management.ts:80-135
**AuthKit path accepts any `client_id` and `redirect_uri` without client-registry validation**

`GET /user_management/authorize` and `POST /user_management/authorize/submit` both call `issueCodeRedirect` without first checking whether the `client_id` is registered or whether `redirect_uri` is among the client's allowed URIs. A test that has a typo in `client_id`, or that points `redirect_uri` at the wrong callback, will still receive a valid authorization code and complete the flow — silently masking the misconfiguration.

The OAuth path avoids exactly this problem via `validateAuthorizeRequest`, which rejects unknown clients with 401 and unregistered redirect URIs with 422. `issueCodeRedirect` should call the same (or an equivalent) guard before minting a code, consistent with the fix already applied to `/oauth2/authorize`.

Reviews (3): Last reviewed commit: "chore: ignore .please/state watcher file..." | Re-trigger Greptile

Comment thread packages/workos/src/routes/oauth.ts
Comment thread packages/posthog/src/routes/oauth.ts
Comment thread packages/spotify/src/routes/token.ts
- workos: bind code/refresh-token redemption to the issuing client_id,
  validate redirect_uri on code exchange, enforce S256 PKCE when a
  code_challenge was recorded, strict organization name validation
- x: revalidate client/redirect_uri/scope at the consent endpoint, require
  redirect_uri on authorize, derive seeded client type from secret presence,
  decrement author tweet_count on delete, declare token/revoke request bodies
  and opt them out of global security in the OpenAPI spec
- posthog: strict integer project ids, enforce client_secret_post at the
  token endpoint for registered confidential clients
- spotify: return stored credentials for duplicate emulator apps, expire
  bearer tokens per expires_in, require q/type on /v1/search
- autumn: settle only completed checkout sessions via the bulk webhook
  stand-in, preserve trial semantics on immediate no-card attaches
@amondnet

amondnet commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 14 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/x/src/routes/oauth.ts
- spotify: skip Basic credentials when the decoded pair has no ':' separator
  instead of deriving garbage clientId/secret
- x: revalidate code_challenge_method=S256 on direct consent POSTs
Comment thread packages/workos/src/routes/oauth.ts
amondnet added 2 commits July 4, 2026 14:23
…thorize

Both GET /oauth2/authorize and POST /oauth2/authorize/submit now reject an
unregistered client_id and a redirect_uri outside the client's registered
list, matching the X emulator's authorize behavior.
@amondnet

amondnet commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@sonarqubecloud

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

Comment on lines +80 to +135
app.get('/user_management/authorize', (c) => {
const clientId = c.req.query('client_id') ?? ''
const redirectUri = c.req.query('redirect_uri') ?? ''
const state = c.req.query('state') ?? ''
// Headless path: ?login_hint=email signs that user straight in (creating
// them if new) — what e2e identity minting uses. No hint → hosted page.
const loginHint = c.req.query('login_hint')
if (loginHint) {
return issueCodeRedirect(c, ws(), loginHint, clientId, redirectUri, state)
}
const users = ws().users.all()
const buttons = users
.map(user =>
renderUserButton({
letter: (user.email[0] ?? '?').toUpperCase(),
login: user.email,
name: `${user.first_name ?? ''} ${user.last_name ?? ''}`.trim() || user.email,
email: user.email,
formAction: `${baseUrl}/user_management/authorize/submit`,
hiddenFields: { email: user.email, client_id: clientId, redirect_uri: redirectUri, state },
}),
)
.join('\n')
const newUserForm = `
<form method="post" action="${baseUrl}/user_management/authorize/submit" class="new-user">
<input type="hidden" name="client_id" value="${escapeHtml(clientId)}" />
<input type="hidden" name="redirect_uri" value="${escapeHtml(redirectUri)}" />
<input type="hidden" name="state" value="${escapeHtml(state)}" />
<input type="email" name="email" class="checkout-input" placeholder="new-user@example.com" required />
<button type="submit" class="checkout-pay-btn">Continue as new user</button>
</form>`
return c.html(
renderCardPage(
'Sign in with AuthKit',
'Pick an existing emulator user or continue as a new one.',
`${buttons}${newUserForm}`,
),
200,
)
})

app.post('/user_management/authorize/submit', async (c) => {
const form = await c.req.parseBody()
const email = String(form.email ?? '')
if (!email) {
return workosError(c, 422, 'invalid_request', 'email is required')
}
return issueCodeRedirect(
c,
ws(),
email,
String(form.client_id ?? ''),
String(form.redirect_uri ?? ''),
String(form.state ?? ''),
)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 AuthKit path accepts any client_id and redirect_uri without client-registry validation

GET /user_management/authorize and POST /user_management/authorize/submit both call issueCodeRedirect without first checking whether the client_id is registered or whether redirect_uri is among the client's allowed URIs. A test that has a typo in client_id, or that points redirect_uri at the wrong callback, will still receive a valid authorization code and complete the flow — silently masking the misconfiguration.

The OAuth path avoids exactly this problem via validateAuthorizeRequest, which rejects unknown clients with 401 and unregistered redirect URIs with 422. issueCodeRedirect should call the same (or an equivalent) guard before minting a code, consistent with the fix already applied to /oauth2/authorize.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/workos/src/routes/user-management.ts
Line: 80-135

Comment:
**AuthKit path accepts any `client_id` and `redirect_uri` without client-registry validation**

`GET /user_management/authorize` and `POST /user_management/authorize/submit` both call `issueCodeRedirect` without first checking whether the `client_id` is registered or whether `redirect_uri` is among the client's allowed URIs. A test that has a typo in `client_id`, or that points `redirect_uri` at the wrong callback, will still receive a valid authorization code and complete the flow — silently masking the misconfiguration.

The OAuth path avoids exactly this problem via `validateAuthorizeRequest`, which rejects unknown clients with 401 and unregistered redirect URIs with 422. `issueCodeRedirect` should call the same (or an equivalent) guard before minting a code, consistent with the fix already applied to `/oauth2/authorize`.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

@amondnet
amondnet merged commit 0ea1419 into main Jul 4, 2026
7 of 8 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 4, 2026
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.

2 participants