Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 2115b2335a Unauthenticated GraphQL requests hit empty schema, producing "Unknown type" errors
https://sonarly.com/issue/3956?type=bug

When auth tokens expire/clear, the frontend sends GraphQL queries without authentication. The server returns an empty schema for unauthenticated requests, causing schema validation errors instead of a proper UNAUTHENTICATED error that the client can handle.

Fix: ## Server fix — `graphql-config.service.ts`

Instead of returning `new GraphQLSchema({})` when the workspace is not resolved (unauthenticated request), throw a `GraphQLError` with `extensions.code: 'UNAUTHENTICATED'`:

```typescript file=packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts lines=87-99
conditionalSchema: async (context) => {
  const { workspace, user, application } = context.req;

  if (!isDefined(workspace)) {
    throw new GraphQLError('Unauthenticated', {
      extensions: {
        code: 'UNAUTHENTICATED',
      },
    });
  }

  try {
    return await this.createSchema(context, workspace, application?.id);
```

The check is moved **before** the `try` block so it propagates as a proper GraphQL error rather than being swallowed by the catch handler. The unused `GraphQLSchema` import was also removed.

## Monitoring noise fix (2a) — `apollo.factory.ts`

Added a guard in the `default` case of the error link to skip Sentry when there is no token pair. Schema-validation errors (which have no `extensions.code`) that arrive when the user is unauthenticated are expected and non-actionable; sending them to Sentry is pure noise:

```typescript file=packages/twenty-front/src/modules/apollo/services/apollo.factory.ts lines=286-293
default:
  // Schema-validation errors (no extension code) caused by
  // unauthenticated requests are expected and non-actionable;
  // skip Sentry when there is no token pair.
  if (isUndefinedOrNull(getTokenPair())) {
    return;
  }
  sendToSentry({ graphQLError, operation });
```

Together these two changes ensure: (1) unauthenticated requests get a proper `UNAUTHENTICATED` error the client can act on, (2) token renewal is triggered instead of broken object pages, and (3) as a defense-in-depth fallback, any residual uncodified GraphQL errors that arrive while unauthenticated are not forwarded to Sentry.
2026-03-06 09:24:17 +00:00
2 changed files with 15 additions and 5 deletions
@@ -284,6 +284,12 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
return; // already caught in BE
}
default:
// Schema-validation errors (no extension code) caused by
// unauthenticated requests are expected and non-actionable;
// skip Sentry when there is no token pair.
if (isUndefinedOrNull(getTokenPair())) {
return;
}
sendToSentry({ graphQLError, operation });
}
}
@@ -7,7 +7,7 @@ import {
type YogaDriverServerContext,
} from '@graphql-yoga/nestjs';
import * as Sentry from '@sentry/node';
import { GraphQLError, GraphQLSchema } from 'graphql';
import { GraphQLError } from 'graphql';
import GraphQLJSON from 'graphql-type-json';
import {
type GraphQLSchemaWithContext,
@@ -87,11 +87,15 @@ export class GraphQLConfigService
conditionalSchema: async (context) => {
const { workspace, user, application } = context.req;
try {
if (!isDefined(workspace)) {
return new GraphQLSchema({});
}
if (!isDefined(workspace)) {
throw new GraphQLError('Unauthenticated', {
extensions: {
code: 'UNAUTHENTICATED',
},
});
}
try {
return await this.createSchema(context, workspace, application?.id);
} catch (error) {
if (error instanceof UnauthorizedException) {