Feat/2fa (#9634)
# Description Closes #7003 Implements 2FA with TOTP. >[!WARNING] > This is a draft PR, with only partial changes, made as a mean of discussion about #7003 (it's easier to reason about real code) ## Behaviour - a `totpSecret` is stored for each user - use [`otplib`](https://github.com/yeojz/otplib/tree/master) to create a QR code and to validate an `otp` against an `totpSecret` (great [demo website](https://otplib.yeojz.dev/) by `otplib`) - OTP is asked upon each login attempt ## Source Inspired by: - [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238) - Cal.com's implementation of 2FA, namely - [raising a 401](c21ba636d2/packages/features/auth/lib/next-auth-options.ts (L188-L190)) when missing OTP and 2FA is enabled, with a [specific error code](c21ba636d2/packages/features/auth/lib/ErrorCode.ts (L9)) - [catching the 401](c21ba636d2/apps/web/modules/auth/login-view.tsx (L160)) in the frontend and [displaying](c21ba636d2/apps/web/modules/auth/login-view.tsx (L276)) the OTP input ## Remaining - [ ] encrypt `totpSecret` at rest using a symetric algorithm --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class TwoFactorMethod1737033794408 implements MigrationInterface {
|
||||
name = 'TwoFactorMethod1737033794408';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "core"."twoFactorMethod" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userWorkspaceId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_752f0250dd6824289ceddd8b054" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."twoFactorMethod" ADD CONSTRAINT "FK_c1044145be65a4ee65c07e0a658" FOREIGN KEY ("userWorkspaceId") REFERENCES "core"."userWorkspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."twoFactorMethod" DROP CONSTRAINT "FK_c1044145be65a4ee65c07e0a658"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "core"."twoFactorMethod"`);
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity';
|
||||
import { WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-factor-method.entity';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@ -47,6 +48,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
BillingEntitlement,
|
||||
PostgresCredentials,
|
||||
WorkspaceSSOIdentityProvider,
|
||||
TwoFactorMethod,
|
||||
],
|
||||
metadataTableName: '_typeorm_generated_columns_and_materialized_views',
|
||||
ssl: environmentService.get('PG_SSL_ALLOW_SELF_SIGNED')
|
||||
|
||||
Reference in New Issue
Block a user