Fix latency spike on application lookup (#21042)
Fixes https://discord.com/channels/1130383047699738754/1509645089062781058 Caching application entities to improve authentication latency
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type FlatAuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type';
|
||||
@@ -13,7 +13,7 @@ declare module 'express-serve-static-core' {
|
||||
interface Request {
|
||||
user?: FlatAuthContextUser | null;
|
||||
apiKey?: FlatApiKey | null;
|
||||
application?: ApplicationEntity | null;
|
||||
application?: FlatApplication | null;
|
||||
userWorkspace?: FlatUserWorkspace;
|
||||
locale: keyof typeof APP_LOCALES;
|
||||
workspace?: FlatWorkspace;
|
||||
|
||||
+9
-5
@@ -80,7 +80,9 @@ export class BackfillRelationJoinColumnIndexesCommand extends ActiveOrSuspendedW
|
||||
|
||||
const indexedFieldIds = new Set<string>();
|
||||
|
||||
for (const flatIndex of Object.values(flatIndexMaps.byUniversalIdentifier)) {
|
||||
for (const flatIndex of Object.values(
|
||||
flatIndexMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(flatIndex)) {
|
||||
continue;
|
||||
}
|
||||
@@ -155,10 +157,12 @@ export class BackfillRelationJoinColumnIndexesCommand extends ActiveOrSuspendedW
|
||||
universalFlatIndexMetadata,
|
||||
joinColumnName,
|
||||
} of flatIndexBuildPlans) {
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
objectMetadata: flatObjectMetadata,
|
||||
});
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration(
|
||||
{
|
||||
workspaceId,
|
||||
objectMetadata: flatObjectMetadata,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceSchemaManagerService.indexManager.createIndex({
|
||||
queryRunner,
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { type ActorMetadata, FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
|
||||
type BuildCreatedByFromApplicationArgs = {
|
||||
application: ApplicationEntity;
|
||||
application: FlatApplication;
|
||||
};
|
||||
export const buildCreatedByFromApplication = ({
|
||||
application,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { ConnectionProviderModule } from 'src/engine/core-modules/application/connection-provider/connection-provider.module';
|
||||
import { ConnectionProviderOAuthController } from 'src/engine/core-modules/application/connection-provider/connection-provider-oauth.controller';
|
||||
@@ -90,7 +89,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
UserEntity,
|
||||
AppTokenEntity,
|
||||
ApiKeyEntity,
|
||||
ApplicationEntity,
|
||||
FeatureFlagEntity,
|
||||
WorkspaceSSOIdentityProviderEntity,
|
||||
KeyValuePairEntity,
|
||||
|
||||
+11
-8
@@ -17,7 +17,6 @@ import { JwtAuthStrategy } from './jwt.auth.strategy';
|
||||
describe('JwtAuthStrategy', () => {
|
||||
let strategy: JwtAuthStrategy;
|
||||
let userWorkspaceRepository: any;
|
||||
let applicationRepository: any;
|
||||
let jwtWrapperService: any;
|
||||
let permissionsService: any;
|
||||
let workspaceCacheService: any;
|
||||
@@ -30,21 +29,19 @@ describe('JwtAuthStrategy', () => {
|
||||
|
||||
let workspaceStore: Record<string, any>;
|
||||
let userStore: Record<string, any>;
|
||||
let applicationStore: Record<string, Record<string, any>>;
|
||||
let apiKeyStore: Record<string, Record<string, any>>;
|
||||
|
||||
beforeEach(() => {
|
||||
workspaceStore = {};
|
||||
userStore = {};
|
||||
applicationStore = {};
|
||||
apiKeyStore = {};
|
||||
|
||||
userWorkspaceRepository = {
|
||||
findOne: jest.fn(),
|
||||
};
|
||||
|
||||
applicationRepository = {
|
||||
findOne: jest.fn(),
|
||||
};
|
||||
|
||||
jwtWrapperService = {
|
||||
extractJwtFromRequest: jest.fn(() => () => 'token'),
|
||||
resolveVerificationKey: jest.fn(async () => ({
|
||||
@@ -80,6 +77,12 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (cacheKeys.includes('flatApplicationMaps')) {
|
||||
result.flatApplicationMaps = {
|
||||
byId: applicationStore[workspaceId] ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
if (cacheKeys.includes('apiKeyMap')) {
|
||||
result.apiKeyMap = apiKeyStore[workspaceId] ?? {};
|
||||
}
|
||||
@@ -116,7 +119,6 @@ describe('JwtAuthStrategy', () => {
|
||||
const createStrategy = () =>
|
||||
new JwtAuthStrategy(
|
||||
jwtWrapperService,
|
||||
applicationRepository,
|
||||
userWorkspaceRepository,
|
||||
permissionsService,
|
||||
workspaceCacheService,
|
||||
@@ -346,9 +348,10 @@ describe('JwtAuthStrategy', () => {
|
||||
workspaceId: validWorkspaceId,
|
||||
};
|
||||
|
||||
workspaceStore[validWorkspaceId] = new WorkspaceEntity();
|
||||
const mockWorkspace = new WorkspaceEntity();
|
||||
|
||||
applicationRepository.findOne.mockResolvedValue(null);
|
||||
mockWorkspace.id = validWorkspaceId;
|
||||
workspaceStore[validWorkspaceId] = mockWorkspace;
|
||||
|
||||
strategy = createStrategy();
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@ import { PassportStrategy } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Strategy, type SecretOrKeyProvider } from 'passport-jwt';
|
||||
import { type SecretOrKeyProvider, Strategy } from 'passport-jwt';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@@ -36,8 +34,6 @@ import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/works
|
||||
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly jwtWrapperService: JwtWrapperService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
@@ -375,9 +371,12 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
|
||||
const applicationId = payload.sub ?? payload.applicationId;
|
||||
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId },
|
||||
});
|
||||
const { flatApplicationMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspace.id, [
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const application = flatApplicationMaps.byId[applicationId];
|
||||
|
||||
if (!isDefined(application)) {
|
||||
throw new AuthException(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type';
|
||||
import { type FlatAuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type';
|
||||
import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
|
||||
export { AUTH_CONTEXT_USER_SELECT_FIELDS } from 'src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants';
|
||||
export { type FlatAuthContextUser as AuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type';
|
||||
@@ -15,7 +15,7 @@ export type RawAuthContext = {
|
||||
workspaceMemberId?: string;
|
||||
workspaceMember?: WorkspaceMemberWorkspaceEntity;
|
||||
workspace?: FlatWorkspace;
|
||||
application?: ApplicationEntity | null | undefined;
|
||||
application?: FlatApplication | null | undefined;
|
||||
userWorkspaceId?: string;
|
||||
userWorkspace?: FlatUserWorkspace;
|
||||
authProvider?: AuthProviderEnum;
|
||||
|
||||
Reference in New Issue
Block a user