diff --git a/libs/api/auth/src/lib/interceptors/auth.interceptor.spec.ts b/libs/api/auth/src/lib/interceptors/auth.interceptor.spec.ts index 634ade45..c3f09d6c 100644 --- a/libs/api/auth/src/lib/interceptors/auth.interceptor.spec.ts +++ b/libs/api/auth/src/lib/interceptors/auth.interceptor.spec.ts @@ -26,10 +26,6 @@ describe('AuthInterceptor', () => { service = new AuthInterceptor(mockAuthUserExtractor); }); - it('should be defined', () => { - expect(service).toBeDefined(); - }); - it('should throw unauthorized http exception', async () => { jest .spyOn(mockAuthUserExtractor, 'getUserFromRequest') diff --git a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts index 2f9b50c6..73ec02a0 100644 --- a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts +++ b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts @@ -17,8 +17,8 @@ describe('SentryOTelUserContextInterceptor', () => { interceptor = new SentryOTelUserContextInterceptor(); }); - it('should be defined', () => { - expect(interceptor).toBeDefined(); + afterEach(() => { + jest.clearAllMocks(); }); it('should set user context and attributes for Sentry and OpenTelemetry', async () => { @@ -66,4 +66,27 @@ describe('SentryOTelUserContextInterceptor', () => { username: `${user.firstName} ${user.lastName}`, }); }); + + it('should ignore user context and attributes for Sentry and OpenTelemetry if no user is present', async () => { + const ctx = createGqlContextForRequest( + createMock({ + user: undefined, + }), + ); + const handler = createMock({ + handle(): Observable { + return of(true); + }, + }); + + const sentrySetUserSpy = jest.spyOn(Sentry, 'setUser'); + const getActiveSpanSpy = jest.spyOn(trace, 'getActiveSpan'); + + await expect( + firstValueFrom(interceptor.intercept(ctx, handler)), + ).resolves.toBeTruthy(); + + expect(sentrySetUserSpy).not.toHaveBeenCalled(); + expect(getActiveSpanSpy).not.toHaveBeenCalled(); + }); }); diff --git a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts index 1a455dfb..03d655cb 100644 --- a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts +++ b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts @@ -4,30 +4,38 @@ import { Injectable, NestInterceptor, } from '@nestjs/common'; -import { GqlExecutionContext } from '@nestjs/graphql'; +import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql'; import { trace } from '@opentelemetry/api'; import * as Sentry from '@sentry/node'; import { Observable } from 'rxjs'; -import { KordisGqlContext } from '@kordis/api/shared'; +import { KordisGqlContext, KordisRequest } from '@kordis/api/shared'; +import { AuthUser } from '@kordis/shared/auth'; @Injectable() export class SentryOTelUserContextInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { - const ctx = GqlExecutionContext.create(context); - const { user } = ctx.getContext().req; + let user: AuthUser | undefined; // user can be undefined, since we have routes passed through the auth interceptor which don't require a user (/health-check) + if (context.getType() === 'graphql') { + const ctx = GqlExecutionContext.create(context); + user = ctx.getContext().req.user; + } else { + user = context.switchToHttp().getRequest().user; + } - trace.getActiveSpan()?.setAttributes({ - 'user.id': user.id, - 'user.email': user.email, - 'user.name': `${user.firstName} ${user.lastName}`, - }); + if (user) { + trace.getActiveSpan()?.setAttributes({ + 'user.id': user.id, + 'user.email': user.email, + 'user.name': `${user.firstName} ${user.lastName}`, + }); - Sentry.setUser({ - id: user.id, - email: user.email, - username: `${user.firstName} ${user.lastName}`, - }); + Sentry.setUser({ + id: user.id, + email: user.email, + username: `${user.firstName} ${user.lastName}`, + }); + } return next.handle(); }