Fix workspace logo, cookie security, and improve workspace impersonation (#14838)
3 small fixes: - Fix workspace logo - Improve cookie security - Improve workspace impersonation
This commit is contained in:
+5
@@ -7,6 +7,7 @@ import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-pan
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
@@ -82,6 +83,10 @@ describe('AdminPanelService', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: FileService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { HealthModule } from 'src/engine/core-modules/health/health.module';
|
||||
import { ImpersonationModule } from 'src/engine/core-modules/impersonation/impersonation.module';
|
||||
import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module';
|
||||
@@ -21,6 +22,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
TypeOrmModule.forFeature([User]),
|
||||
AuthModule,
|
||||
DomainManagerModule,
|
||||
FileModule,
|
||||
HealthModule,
|
||||
RedisClientModule,
|
||||
TerminusModule,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { type FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { type ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
|
||||
import { CONFIG_VARIABLES_GROUP_METADATA } from 'src/engine/core-modules/twenty-config/constants/config-variables-group-metadata';
|
||||
import { type ConfigVariablesGroup } from 'src/engine/core-modules/twenty-config/enums/config-variables-group.enum';
|
||||
@@ -34,15 +35,21 @@ export class AdminPanelService {
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly auditService: AuditService,
|
||||
private readonly fileService: FileService,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
async userLookup(userIdentifier: string): Promise<UserLookup> {
|
||||
const isEmail = userIdentifier.includes('@');
|
||||
const normalizedIdentifier = isEmail
|
||||
? userIdentifier.toLowerCase()
|
||||
: userIdentifier;
|
||||
|
||||
const targetUser = await this.userRepository.findOne({
|
||||
where: isEmail ? { email: userIdentifier } : { id: userIdentifier },
|
||||
where: isEmail
|
||||
? { email: normalizedIdentifier }
|
||||
: { id: normalizedIdentifier },
|
||||
relations: {
|
||||
userWorkspaces: {
|
||||
workspace: {
|
||||
@@ -57,7 +64,9 @@ export class AdminPanelService {
|
||||
|
||||
userValidator.assertIsDefinedOrThrow(
|
||||
targetUser,
|
||||
new AuthException('User not found', AuthExceptionCode.INVALID_INPUT),
|
||||
new AuthException('User not found', AuthExceptionCode.INVALID_INPUT, {
|
||||
userFriendlyMessage: 'User not found. Please check the email or ID.',
|
||||
}),
|
||||
);
|
||||
|
||||
const allFeatureFlagKeys = Object.values(FeatureFlagKey);
|
||||
@@ -73,8 +82,18 @@ export class AdminPanelService {
|
||||
id: userWorkspace.workspace.id,
|
||||
name: userWorkspace.workspace.displayName ?? '',
|
||||
totalUsers: userWorkspace.workspace.workspaceUsers.length,
|
||||
logo: userWorkspace.workspace.logo,
|
||||
logo: userWorkspace.workspace.logo
|
||||
? this.fileService.signFileUrl({
|
||||
url: userWorkspace.workspace.logo,
|
||||
workspaceId: userWorkspace.workspace.id,
|
||||
})
|
||||
: userWorkspace.workspace.logo,
|
||||
allowImpersonation: userWorkspace.workspace.allowImpersonation,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls({
|
||||
subdomain: userWorkspace.workspace.subdomain,
|
||||
customDomain: userWorkspace.workspace.customDomain,
|
||||
isCustomDomainEnabled: userWorkspace.workspace.isCustomDomainEnabled,
|
||||
}),
|
||||
users: userWorkspace.workspace.workspaceUsers.map((workspaceUser) => ({
|
||||
id: workspaceUser.user.id,
|
||||
email: workspaceUser.user.email,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
|
||||
|
||||
@ObjectType()
|
||||
class UserInfo {
|
||||
@@ -35,6 +36,9 @@ class WorkspaceInfo {
|
||||
@Field(() => Number)
|
||||
totalUsers: number;
|
||||
|
||||
@Field(() => WorkspaceUrls)
|
||||
workspaceUrls: WorkspaceUrls;
|
||||
|
||||
@Field(() => [UserInfo])
|
||||
users: UserInfo[];
|
||||
|
||||
|
||||
+4
-2
@@ -2,14 +2,16 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { ApprovedAccessDomainResolver } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.resolver';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DomainManagerModule,
|
||||
FileModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ApprovedAccessDomain]),
|
||||
],
|
||||
exports: [ApprovedAccessDomainService],
|
||||
|
||||
+11
-1
@@ -18,6 +18,7 @@ import {
|
||||
import { approvedAccessDomainValidator } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.validate';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
@@ -31,6 +32,7 @@ export class ApprovedAccessDomainService {
|
||||
private readonly emailService: EmailService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly fileService: FileService,
|
||||
) {}
|
||||
|
||||
async sendApprovedAccessDomainValidationEmail(
|
||||
@@ -70,7 +72,15 @@ export class ApprovedAccessDomainService {
|
||||
|
||||
const emailTemplate = SendApprovedAccessDomainValidation({
|
||||
link: link.toString(),
|
||||
workspace: { name: workspace.displayName, logo: workspace.logo },
|
||||
workspace: {
|
||||
name: workspace.displayName,
|
||||
logo: workspace.logo
|
||||
? this.fileService.signFileUrl({
|
||||
url: workspace.logo,
|
||||
workspaceId: workspace.id,
|
||||
})
|
||||
: workspace.logo,
|
||||
},
|
||||
domain: approvedAccessDomain.domain,
|
||||
sender: {
|
||||
email: sender.userEmail,
|
||||
|
||||
+9
@@ -12,6 +12,7 @@ import {
|
||||
} from 'src/engine/core-modules/approved-access-domain/approved-access-domain.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
@@ -67,6 +68,14 @@ describe('ApprovedAccessDomainService', () => {
|
||||
buildWorkspaceURL: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: FileService,
|
||||
useValue: {
|
||||
signFileUrl: jest
|
||||
.fn()
|
||||
.mockReturnValue('https://signed-url.com/logo.png'),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+2
@@ -32,6 +32,8 @@ export const getSessionStorageOptions = (
|
||||
proxy: true,
|
||||
cookie: {
|
||||
secure: !!(SERVER_URL && SERVER_URL.startsWith('https')),
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
maxAge: 1000 * 60 * 30, // 30 minutes
|
||||
},
|
||||
};
|
||||
|
||||
+6
-1
@@ -381,7 +381,12 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
id: workspace.id,
|
||||
displayName: workspace.displayName,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(workspace),
|
||||
logo: workspace.logo,
|
||||
logo: workspace.logo
|
||||
? this.fileService.signFileUrl({
|
||||
url: workspace.logo,
|
||||
workspaceId: workspace.id,
|
||||
})
|
||||
: workspace.logo,
|
||||
sso:
|
||||
workspace.workspaceSSOIdentityProviders?.reduce(
|
||||
(acc, identityProvider) =>
|
||||
|
||||
+10
-1
@@ -9,6 +9,8 @@ import {
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -16,7 +18,6 @@ import { WorkspaceInvitationException } from 'src/engine/core-modules/workspace-
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
|
||||
import { WorkspaceInvitationService } from './workspace-invitation.service';
|
||||
|
||||
@@ -103,6 +104,14 @@ describe('WorkspaceInvitationService', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: FileService,
|
||||
useValue: {
|
||||
signFileUrl: jest
|
||||
.fn()
|
||||
.mockReturnValue('https://signed-url.com/logo.png'),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+11
-1
@@ -22,6 +22,7 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -47,6 +48,7 @@ export class WorkspaceInvitationService {
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly fileService: FileService,
|
||||
) {}
|
||||
|
||||
async validatePersonalInvitation({
|
||||
@@ -294,7 +296,15 @@ export class WorkspaceInvitationService {
|
||||
|
||||
const emailData = {
|
||||
link: link.toString(),
|
||||
workspace: { name: workspace.displayName, logo: workspace.logo },
|
||||
workspace: {
|
||||
name: workspace.displayName,
|
||||
logo: workspace.logo
|
||||
? this.fileService.signFileUrl({
|
||||
url: workspace.logo,
|
||||
workspaceId: workspace.id,
|
||||
})
|
||||
: workspace.logo,
|
||||
},
|
||||
sender: {
|
||||
email: sender.userEmail,
|
||||
firstName: sender.name.firstName,
|
||||
|
||||
Reference in New Issue
Block a user