Fix server logs leak (#18423)

# Introduction

Previously the auth jwt stragegy would lod the whole user entity in the
auth user context
On an exception it would completely get logged on the pods


## Security layer
- 0/ Updating the type system ( devxp only though )
- 1/ The jwt auth stragegy only load a specific sub set of the user
entity
- 2/ Sanitizing at the exception log level directly in case of a user
context
- 3/ Sanitizing at the console driver

The last two sanitization could sound a bit redundant though they're
still good fallback to keep in case new path occurs in the cb
This commit is contained in:
Paul Rastoin
2026-03-05 14:40:23 +01:00
committed by GitHub
parent 647c32ff3e
commit 38ad0820c0
31 changed files with 147 additions and 75 deletions
@@ -45,6 +45,7 @@ import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/r
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
import {
AuthContextUser,
JwtTokenTypeEnum,
LoginTokenJwtPayload,
} from 'src/engine/core-modules/auth/types/auth-context.type';
@@ -508,7 +509,7 @@ export class AuthResolver {
@Mutation(() => SignUpDTO)
@UseGuards(UserAuthGuard, NoPermissionGuard)
async signUpInNewWorkspace(
@AuthUser() currentUser: UserEntity,
@AuthUser() currentUser: AuthContextUser,
@AuthProvider() authProvider: AuthProviderEnum,
): Promise<SignUpDTO> {
const { user, workspace } = await this.signInUpService.signUpOnNewWorkspace(
@@ -533,7 +534,7 @@ export class AuthResolver {
@Mutation(() => TransientTokenDTO)
@UseGuards(UserAuthGuard, NoPermissionGuard)
async generateTransientToken(
@AuthUser() user: UserEntity,
@AuthUser() user: AuthContextUser,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<TransientTokenDTO | void> {
const workspaceMember = await this.userService.loadWorkspaceMember(
@@ -780,7 +781,7 @@ export class AuthResolver {
@UseGuards(UserAuthGuard, NoPermissionGuard)
async authorizeApp(
@Args() authorizeAppInput: AuthorizeAppInput,
@AuthUser() user: UserEntity,
@AuthUser() user: AuthContextUser,
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<AuthorizeAppDTO> {
return await this.authService.generateAuthorizationCode(
@@ -0,0 +1,17 @@
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
export const AUTH_CONTEXT_USER_SELECT_FIELDS = [
'id',
'firstName',
'lastName',
'email',
'defaultAvatarUrl',
'isEmailVerified',
'disabled',
'canImpersonate',
'canAccessFullAdminPanel',
'createdAt',
'updatedAt',
'deletedAt',
'locale',
] as const satisfies ReadonlyArray<keyof UserEntity>;
@@ -13,11 +13,11 @@ import { AppPath } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import {
AppTokenEntity,
AppTokenType,
} from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
import {
AuthException,
@@ -29,7 +29,6 @@ import {
hashPassword,
} from 'src/engine/core-modules/auth/auth.util';
import { type AuthTokens } from 'src/engine/core-modules/auth/dto/auth-tokens.dto';
import { validateRedirectUri } from 'src/engine/core-modules/auth/utils/validate-redirect-uri.util';
import { type AuthorizeAppDTO } from 'src/engine/core-modules/auth/dto/authorize-app.dto';
import { type AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input';
import { type UpdatePasswordDTO } from 'src/engine/core-modules/auth/dto/update-password.dto';
@@ -44,13 +43,17 @@ import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
import {
AuthContextUser,
JwtTokenTypeEnum,
} from 'src/engine/core-modules/auth/types/auth-context.type';
import {
type AuthProviderWithPasswordType,
type ExistingUserOrNewUser,
type SignInUpBaseParams,
type SignInUpNewUserPayload,
} from 'src/engine/core-modules/auth/types/signInUp.type';
import { validateRedirectUri } from 'src/engine/core-modules/auth/utils/validate-redirect-uri.util';
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
import { WorkspaceDomainConfig } from 'src/engine/core-modules/domain/workspace-domains/types/workspace-domain-config.type';
@@ -494,7 +497,7 @@ export class AuthService {
async generateAuthorizationCode(
authorizeAppInput: AuthorizeAppInput,
user: UserEntity,
user: AuthContextUser,
workspace: WorkspaceEntity,
): Promise<AuthorizeAppDTO> {
const { clientId, codeChallenge } = authorizeAppInput;
@@ -36,6 +36,7 @@ import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-cli
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
@@ -312,7 +313,7 @@ export class SignInUpService {
workspace,
shouldShowConnectAccountStep,
}: {
user: UserEntity;
user: AuthContextUser;
workspace: WorkspaceEntity;
shouldShowConnectAccountStep: boolean;
},
@@ -19,7 +19,9 @@ import {
type AccessTokenJwtPayload,
type ApiKeyTokenJwtPayload,
ApplicationAccessTokenJwtPayload,
AUTH_CONTEXT_USER_SELECT_FIELDS,
type AuthContext,
type AuthContextUser,
FileTokenJwtPayloadLegacy,
type JwtPayload,
JwtTokenTypeEnum,
@@ -118,7 +120,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
private async validateAccessToken(
payload: AccessTokenJwtPayload,
): Promise<AuthContext> {
let user: UserEntity | null = null;
let user: AuthContextUser | null = null;
let context: AuthContext = {};
const workspace = await this.workspaceRepository.findOneBy({
@@ -221,11 +223,12 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
userWorkspaceId: string;
expectedWorkspaceId?: string;
}): Promise<{
user: UserEntity;
user: AuthContextUser;
userWorkspace: UserWorkspaceEntity;
} | null> {
const user = await this.userRepository.findOne({
where: { id: params.userId },
select: [...AUTH_CONTEXT_USER_SELECT_FIELDS],
});
if (!isDefined(user)) {
@@ -234,7 +237,6 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
const userWorkspace = await this.userWorkspaceRepository.findOne({
where: { id: params.userWorkspaceId },
relations: ['user', 'workspace'],
});
if (!isDefined(userWorkspace)) {
@@ -243,7 +245,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
if (
isDefined(params.expectedWorkspaceId) &&
userWorkspace.workspace.id !== params.expectedWorkspaceId
userWorkspace.workspaceId !== params.expectedWorkspaceId
) {
return null;
}
@@ -348,6 +350,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
): Promise<AuthContext> {
const user = await this.userRepository.findOne({
where: { id: payload.sub },
select: [...AUTH_CONTEXT_USER_SELECT_FIELDS],
});
userValidator.assertIsDefinedOrThrow(
@@ -0,0 +1,7 @@
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
import { type AUTH_CONTEXT_USER_SELECT_FIELDS } from 'src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants';
export type AuthContextUser = Pick<
UserEntity,
(typeof AUTH_CONTEXT_USER_SELECT_FIELDS)[number]
>;
@@ -1,13 +1,16 @@
import { type ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context-user.type';
import { type UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export { AUTH_CONTEXT_USER_SELECT_FIELDS } from 'src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants';
export { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context-user.type';
export type AuthContext = {
user?: UserEntity | null | undefined;
user?: AuthContextUser | null | undefined;
apiKey?: ApiKeyEntity | null | undefined;
workspaceMemberId?: string;
workspaceMember?: WorkspaceMemberWorkspaceEntity;
@@ -1,6 +1,7 @@
import { type APP_LOCALES } from 'twenty-shared/translations';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -41,7 +42,7 @@ export type ExistingUserOrNewUser = {
export type ExistingUserOrPartialUserWithPicture = {
userData:
| { type: 'existingUser'; existingUser: UserEntity }
| { type: 'existingUser'; existingUser: AuthContextUser }
| {
type: 'newUserWithPicture';
newUserWithPicture: PartialUserWithPicture;