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.
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
+9
-5
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user