Migrate web auth from localStorage token pairs to httpOnly cookie sessions (#23642)

This commit is contained in:
Félix Malfait
2026-08-03 19:54:54 +02:00
committed by GitHub
parent f8e3fd110d
commit 267ecb12db
141 changed files with 7339 additions and 1463 deletions
@@ -14,6 +14,7 @@ import { RotateSigningKeysCronCommand } from 'src/engine/core-modules/jwt/crons/
import { CronTriggerCronCommand } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/cron/cron-trigger.cron.command';
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UserSessionCleanupCronCommand } from 'src/engine/core-modules/user-session/crons/commands/user-session-cleanup.cron.command';
import { CheckCustomDomainValidRecordsCronCommand } from 'src/engine/core-modules/workspace/crons/commands/check-custom-domain-valid-records.cron.command';
import { WebhookSubscriptionRenewalCronCommand } from 'src/modules/connected-account/webhook-subscription-manager/crons/commands/webhook-subscription-renewal.cron.command';
import { TrashCleanupCronCommand } from 'src/engine/trash-cleanup/commands/trash-cleanup.cron.command';
@@ -73,6 +74,7 @@ export class CronRegisterAllCommand extends CommandRunner {
private readonly staleRegistrationCleanupCronCommand: StaleRegistrationCleanupCronCommand,
private readonly pendingFileCleanupCronCommand: PendingFileCleanupCronCommand,
private readonly billingReminderCronCommand: BillingReminderCronCommand,
private readonly userSessionCleanupCronCommand: UserSessionCleanupCronCommand,
private readonly twentyConfigService: TwentyConfigService,
) {
super();
@@ -207,6 +209,10 @@ export class CronRegisterAllCommand extends CommandRunner {
command: this.billingReminderCronCommand,
isEnabled: isBillingEnabled,
},
{
name: 'UserSessionCleanup',
command: this.userSessionCleanupCronCommand,
},
];
let successCount = 0;
@@ -32,6 +32,7 @@ import { RotateSigningKeysCronCommand } from 'src/engine/core-modules/jwt/crons/
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { UserSessionModule } from 'src/engine/core-modules/user-session/user-session.module';
import { UpgradeStatusCommand } from 'src/engine/core-modules/upgrade/commands/upgrade-status.command';
import { UpgradeModule } from 'src/engine/core-modules/upgrade/upgrade.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -99,6 +100,7 @@ import { WorkflowCoreConsistencyModule } from 'src/modules/workflow/workflow-cor
WorkspaceVersionModule,
UpgradeModule,
SecretEncryptionRotationModule,
UserSessionModule,
],
providers: [
DataSeedWorkspaceCommand,
@@ -0,0 +1,63 @@
import { type QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
@RegisteredInstanceCommand('2.27.0', 1785518325511)
export class CreateUserSessionCoreTableFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "core"."userSession" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"tokenHash" text NOT NULL,
"userId" uuid NOT NULL,
"workspaceId" uuid,
"userWorkspaceId" uuid,
"authProvider" text NOT NULL,
"isImpersonating" boolean NOT NULL DEFAULT false,
"impersonatorUserWorkspaceId" uuid,
"impersonatedUserWorkspaceId" uuid,
"userAgent" text,
"ipAddress" text,
"expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"lastActiveAt" TIMESTAMP WITH TIME ZONE NOT NULL,
"revokedAt" TIMESTAMP WITH TIME ZONE,
"revokedReason" text,
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
CONSTRAINT "PK_userSession_id" PRIMARY KEY ("id"),
CONSTRAINT "FK_USER_SESSION_USER_ID" FOREIGN KEY ("userId")
REFERENCES "core"."user"("id") ON DELETE CASCADE,
CONSTRAINT "FK_USER_SESSION_WORKSPACE_ID" FOREIGN KEY ("workspaceId")
REFERENCES "core"."workspace"("id") ON DELETE CASCADE,
CONSTRAINT "FK_USER_SESSION_USER_WORKSPACE_ID" FOREIGN KEY ("userWorkspaceId")
REFERENCES "core"."userWorkspace"("id") ON DELETE CASCADE
)`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_USER_SESSION_TOKEN_HASH_UNIQUE" ON "core"."userSession" ("tokenHash")`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_USER_SESSION_USER_ID" ON "core"."userSession" ("userId")`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_USER_SESSION_WORKSPACE_ID" ON "core"."userSession" ("workspaceId")`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_USER_SESSION_USER_WORKSPACE_ID" ON "core"."userSession" ("userWorkspaceId")`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_USER_SESSION_EXPIRES_AT" ON "core"."userSession" ("expiresAt")`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_USER_SESSION_REVOKED_AT" ON "core"."userSession" ("revokedAt")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS "core"."userSession"`);
}
}
@@ -132,6 +132,7 @@ import { AddChannelWebhookSubscriptionExternalIdIndexesFastInstanceCommand } fro
import { AddIsHiddenToAgentMessageFastInstanceCommand } from './2-25/2-25-instance-command-fast-1785230296000-add-is-hidden-to-agent-message';
import { AddConnectedAccountHandleProviderIndexFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-instance-command-fast-1785420705255-add-connected-account-handle-provider-index';
import { AddOpenRecordInToObjectMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-instance-command-fast-1785504900000-add-open-record-in-to-object-metadata';
import { CreateUserSessionCoreTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-27/2-27-instance-command-fast-1785518325511-create-user-session-core-table';
export const INSTANCE_COMMANDS = [
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
@@ -266,4 +267,5 @@ export const INSTANCE_COMMANDS = [
AddIsHiddenToAgentMessageFastInstanceCommand,
AddConnectedAccountHandleProviderIndexFastInstanceCommand,
AddOpenRecordInToObjectMetadataFastInstanceCommand,
CreateUserSessionCoreTableFastInstanceCommand,
];