Use app's own OAuth credentials for CoreApiClient generation (#19563)

## Summary

- **SDK (`dev` & `dev --once`)**: After app registration, the CLI now
obtains an `APPLICATION_ACCESS` token via `client_credentials` grant
using the app's own `clientId`/`clientSecret`, and uses that token for
CoreApiClient schema introspection — instead of the user's
`config.accessToken` which returns the full unscoped schema.
- **Config**: `oauthClientSecret` is now persisted alongside
`oauthClientId` in `~/.twenty/config.json` when creating a new app
registration, so subsequent `dev`/`dev --once` runs can obtain fresh app
tokens without re-registration.
- **CI action**: `spawn-twenty-app-dev-test` now outputs a proper
`API_KEY` JWT (signed with the seeded dev workspace secret) instead of
the previous hardcoded `ACCESS` token — giving consumers a real API key
rather than a user session token.

## Motivation

When developing Twenty apps, `yarn twenty dev` was using the CLI user's
OAuth token for GraphQL schema introspection during CoreApiClient
generation. This token (type `ACCESS`) has no `applicationId` claim, so
the server returns the **full workspace schema** — including all objects
— rather than the scoped schema the app should see at runtime (filtered
by `applicationId`).

This caused a discrepancy: the generated CoreApiClient contained fields
the app couldn't actually query at runtime with its `APPLICATION_ACCESS`
token.

By switching to `client_credentials` grant, the SDK now introspects with
the same token type the app will use in production, ensuring the
generated client accurately reflects the app's runtime capabilities.
This commit is contained in:
Charles Bochet
2026-04-11 11:24:28 +02:00
committed by GitHub
parent f52d66b960
commit c26c0b9d71
22 changed files with 263 additions and 142 deletions
@@ -20,7 +20,8 @@ const mockApiService = {
id: 'mock-registration-id',
oAuthClientId: 'mock-client-id',
},
clientSecret: 'mock-client-secret',
accessToken: 'mock-app-access-token',
refreshToken: 'mock-app-refresh-token',
},
}),
createDevelopmentApplication: vi.fn().mockResolvedValue({
@@ -56,6 +57,12 @@ vi.mock('@/cli/utilities/file/file-uploader', () => ({
},
}));
vi.mock('@/cli/utilities/auth/resolve-app-access-token', () => ({
ensureValidAppAccessTokenOrRefresh: vi
.fn()
.mockResolvedValue('mock-app-access-token'),
}));
vi.mock('@/cli/utilities/client/client-service', () => ({
ClientService: class {
generateCoreClient = vi.fn().mockResolvedValue(undefined);