-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/2fa #9634
Feat/2fa #9634
Conversation
packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts
Outdated
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts
Outdated
Show resolved
Hide resolved
b24e435
to
e876cc3
Compare
@Field(() => [TwoFactorMethod], { nullable: true }) | ||
@OneToMany( | ||
() => TwoFactorMethod, | ||
(twoFactorMethod) => twoFactorMethod.userWorkspace, | ||
) | ||
twoFactorMethods: Relation<TwoFactorMethod[]>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the twoFactorMethods
relation in UserWorkspace
, but for some reason, typeorm can't seem to find the association
/Users/nicolasrouanne/dev/twenty/src/metadata-builder/EntityMetadataBuilder.ts:1111
entityMetadata.relations.forEach((relation) => {
^
TypeORMError: Entity metadata for UserWorkspace#twoFactorMethods was not found. Check if you specified a correct entity object and if it's connected in the connection options.
at /Users/nicolasrouanne/dev/twenty/src/metadata-builder/EntityMetadataBuilder.ts:1121:23
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a quick chat with @FelixMalfait on Discord, it's probably a module-wiring issue. We are going to add the TwoFactorModule
to AuthModule
, to ensure it's properly loaded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually fixed by @FelixMalfait on 59f84ba 🙏🏻
- also add a `is2FAEnabled` column to `userWorkspace` entity
e876cc3
to
a4a360c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good first step! Thank you!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR introduces the initial database and service layer for Two-Factor Authentication (2FA) using TOTP (Time-based One-Time Password).
- Missing critical
totpSecret
column inTwoFactorMethod
entity and migration for storing TOTP secrets TwoFactorMethodService
needs additional methods for TOTP validation and secret generation using theotplib
package- Consider adding status/type fields to
TwoFactorMethod
entity to track enabled/disabled state and authentication method type - Security concern: Need to implement encryption for TOTP secrets at rest as noted in PR description
UserWorkspace
toTwoFactorMethod
relationship allows multiple 2FA methods per user, but business logic for handling multiple methods isn't implemented yet
💡 (3/5) Reply to the bot's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!
8 file(s) reviewed, 8 comment(s)
Edit PR Review Bot Settings | Greptile
...twenty-server/src/database/typeorm/core/migrations/common/1737033794408-two-factor-method.ts
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/two-factor-method/two-factor-method.entity.ts
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/two-factor-method/two-factor-method.service.ts
Show resolved
Hide resolved
async findAll(): Promise<TwoFactorMethod[]> { | ||
return this.twoFactorMethodRepository.find(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: findAll() without any filters could expose all 2FA methods. Consider adding workspace-level filtering for security.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole service file was added as a proof of concept. It might be nice to clean it a bit. Can do it this afternoon.
async remove(id: string): Promise<void> { | ||
await this.twoFactorMethodRepository.delete(id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: No verification that the caller has permission to remove the 2FA method. Add authorization check.
@OneToMany( | ||
() => TwoFactorMethod, | ||
(twoFactorMethod) => twoFactorMethod.userWorkspace, | ||
) | ||
twoFactorMethods: Relation<TwoFactorMethod[]>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: missing @field decorator for GraphQL exposure of twoFactorMethods - this field won't be queryable via GraphQL
@OneToMany( | ||
() => TwoFactorMethod, | ||
(twoFactorMethod) => twoFactorMethod.userWorkspace, | ||
) | ||
twoFactorMethods: Relation<TwoFactorMethod[]>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: consider adding cascade option to automatically handle deletion of associated TwoFactorMethod records
|
||
@Entity({ name: 'twoFactorMethod', schema: 'core' }) | ||
@ObjectType('TwoFactorMethod') | ||
export class TwoFactorMethod { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused, this table does provide any information? no type, token, etc. why is it useful then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because this PR is heavily WIP, nothing is really done. But:
- the token will be derived from the Primary Key of this table
- we only have one 2FA type which is TOTP, that's why we don't store it
Does it make sense?
|
||
import { Repository } from 'typeorm'; | ||
|
||
import { TwoFactorMethod } from './two-factor-method.entity'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
general about the naming: shouldn't we use two-factor-authentication instead of two-factor-method which seems more standard?
- for data model: two-factor-method makes sense as a shortcut for two-factor-authentication-method
- but for the module I would name it two-factor-authentication
return this.twoFactorMethodRepository.save(twoFactorMethod); | ||
} | ||
|
||
async findAll(): Promise<TwoFactorMethod[]> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this shouldn't exist IMO:
- if this endpoint is meant to be part of the "engine api" long term (usable by business modules), I don't think we should authorize such a getter (findAll)
- if this endpoint is meant to be used within the engine, it's not useful as we can just call the ORM direclty and there is no additional business logic to wrap
} | ||
|
||
async findOne(id: string): Promise<TwoFactorMethod | null> { | ||
return this.twoFactorMethodRepository.findOne({ where: { id } }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
} | ||
|
||
async remove(id: string): Promise<void> { | ||
await this.twoFactorMethodRepository.delete(id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@charlesBochet I see your review and I'm sorry about a few things:
Now that I'm unblocked, I can add unit tests, auth logic in the resolvers, and remove all the cruft before merging. Frontend can be done in a separate task, but this is probably too raw right now to merge as is. |
Sorry @nicolasrouanne I think there was a miscommunication between me and @charlesBochet - agree with your points and keep the discussed approach which is to do partial merges along the way |
Ok, let's merge it, my commentaries are non blocking. More than happy with the iterative paths as long as the comments are addressed or discussed in upcoming PRs |
Log
|
# Description Closes twentyhq#7003 Implements 2FA with TOTP. >[!WARNING] > This is a draft PR, with only partial changes, made as a mean of discussion about twentyhq#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](https://github.com/calcom/cal.com/blob/c21ba636d2bec4ed55775f0b058f70fdc371c410/packages/features/auth/lib/next-auth-options.ts#L188-L190) when missing OTP and 2FA is enabled, with a [specific error code](https://github.com/calcom/cal.com/blob/c21ba636d2bec4ed55775f0b058f70fdc371c410/packages/features/auth/lib/ErrorCode.ts#L9) - [catching the 401](https://github.com/calcom/cal.com/blob/c21ba636d2bec4ed55775f0b058f70fdc371c410/apps/web/modules/auth/login-view.tsx#L160) in the frontend and [displaying](https://github.com/calcom/cal.com/blob/c21ba636d2bec4ed55775f0b058f70fdc371c410/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>
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
totpSecret
is stored for each userotplib
to create a QR code and to validate anotp
against antotpSecret
(great demo website byotplib
)Source
Inspired by:
Remaining
totpSecret
at rest using a symetric algorithm