feat: port autumn, gitlab, posthog, spotify, workos, x emulators from UsefulSoftwareCo fork#18
Conversation
… 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.
|
Warning Review limit reached
Next review available in: 54 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (86)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 11 high |
| ErrorProne | 6 medium 28 high |
| Security | 2 critical 10 high |
🟢 Metrics 1279 complexity · 113 duplication
Metric Results Complexity 1279 Duplication 113
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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
- 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%
|
/gemini review |
There was a problem hiding this comment.
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
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Greptile SummaryThis 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.
Confidence Score: 4/5Safe 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
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
%%{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
Prompt To Fix All With AIFix 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 |
- 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
|
/gemini review |
There was a problem hiding this comment.
1 issue found across 14 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- 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
…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.
|
/gemini review |
|
| 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 ?? ''), | ||
| ) | ||
| }) |
There was a problem hiding this 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.
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.


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.Changes
packages/<service>named@pleaseai/emulate-<service>for autumn, gitlab, posthog, spotify, workos, x, all built against published@emulators/core ^0.6.0ServiceManifest/manifest.ts,/_emulatefault injectionbun:testautumnbalances.trackresponse to match theautumn-jsTrackResponseschema (autumn-jspinned to1.2.28)skills/<service>/SKILL.mdfilesExcluded from this port
cloudflare— Durable Object hosting infra, not an emulatormcp— depends on fork-only@emulators/githuband coreLedgerEntryTest Plan
bun run lint— all greenbun run build— all greenbun run type-check— all greenbun 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.trackaligns withautumn-jsTrackResponse.@pleaseai/emulate-gitlab:POST /api/graphqlwith 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.Bug Fixes
client_idandredirect_urion authorize; bind code/refresh redemption to issuingclient_id; validateredirect_urion token exchange; enforce S256 PKCE when present; stricter org name validation.redirect_urion authorize; revalidate client/redirect_uri/scope at consent; revalidatecode_challenge_method=S256on direct consent; derive client type from secret presence; decrement authortweet_counton delete.client_secret_postat token endpoint for confidential clients.expires_in; requireqandtypeon/v1/search; ignore Basic auth headers without a:separator.Math.randomwithnode:cryptoin WorkOS id/token/jti generation and X snowflake id generator (Sonar typescript:S2245).Written for commit 672c5be. Summary will update on new commits.