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:
@@ -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(
|
||||
|
||||
+17
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user