Files
twenty/packages/twenty-server/test/integration/metadata/suites/application/utils/generate-application-token.util.ts
T
Charles Bochet 9bc63a01c9 Generate GQL schema based on applicationId (#17860)
## Add application-scoped GraphQL schema generation

When an application token is used to authenticate, the `/graphql` schema
is now dynamically filtered to only include entities belonging to that
application (plus the Twenty Standard Application). This enables
third-party applications and the SDK to introspect a schema that is
relevant to their scope, rather than seeing the full workspace schema
with all custom objects.

### Changes

- **New `generateApplicationToken` mutation** on the `/metadata`
endpoint, allowing callers to exchange an API key for an
application-scoped JWT token
- **Schema filtering by application** in `WorkspaceSchemaFactory` — when
`request.application` is present (from an application token), flat
entity maps are filtered by `[appId, standardAppId]` before schema
generation
- **Per-app caching** — both the Yoga in-memory cache and Redis cache
now include the `appId` in their keys to avoid serving wrong schemas
- **Consolidated `getSubFlatEntityMapsByApplicationIdsOrThrow`** —
unified the single-ID and multi-ID filtering utilities into one
- **Integration tests** covering token generation (admin + API key auth)
and schema introspection filtering (standard app token excludes custom
objects)

Schema generated on seeds with applicationToken (see that pets is
missing)
<img width="782" height="994" alt="image"
src="https://github.com/user-attachments/assets/82510031-0965-435d-bc26-77c9f5d74e1f"
/>
2026-02-11 20:21:58 +01:00

42 lines
1.5 KiB
TypeScript

import { generateApplicationTokenQueryFactory } from 'test/integration/metadata/suites/application/utils/generate-application-token-query-factory.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
export const generateApplicationToken = async ({
applicationId,
expectToFail = false,
token,
}: {
applicationId: string;
expectToFail?: boolean;
token?: string;
}): CommonResponseBody<{
generateApplicationToken: AuthToken;
}> => {
const graphqlOperation = generateApplicationTokenQueryFactory({
applicationId,
});
const response = await makeMetadataAPIRequest(graphqlOperation, token);
if (expectToFail === true) {
warnIfNoErrorButExpectedToFail({
response,
errorMessage: 'Generate application token should have failed but did not',
});
}
if (expectToFail === false) {
warnIfErrorButNotExpectedToFail({
response,
errorMessage: 'Generate application token has failed but should not',
});
}
return { data: response.body.data, errors: response.body.errors };
};