-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Implement Two-Factor Authentication (2FA) #13141
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
Implement Two-Factor Authentication (2FA) #13141
Conversation
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com>
…tyhq#13048) to resolve the issue twentyhq#13047 It's my first contribution, I hope it's good enough! When using the slash command (/) in the notes editor, a menu of commands appears. When navigating this menu with the up and down arrow keys, the selection changes, but the menu itself does not scroll. This means that as the list of commands is long, items outside the visible area cannot be seen when selected. The issue was located in the [CustomSlashMenu.tsx] component. The menu container didn't have vertical scrolling enabled, and there was no logic to scroll the active item into the visible area. The fix involved: Adding overflow-y: auto to the menu's styled container in [CustomSlashMenu.tsx]. Modifying the [DropdownMenuItemsContainer] component to accept and forward a ref using React.forwardRef. Implementing a useEffect hook in [CustomSlashMenu.tsx] that triggers on selection change. This hook uses a ref to the items container to call scrollIntoView({ block: 'nearest' }) on the currently selected menu item. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
its readily available when generateLoginToken is called, and will be used by getAuthTokensFromOTP to find user's 2FA method for the given workspace
…rn userWorkspace's workspace TO REVIEW: twoFactorAuthentication service needs only the workspace's display name, maybe a partial relation selection using QueryBuilder could work best here
…ty and workspace-update-input dto this property allows for each workspace to set its own policy. Currently holds only one property strategy, but can easily be extended to hold workspace specific configuration for both policy and strategy.
…e relationhship at the moment, this is a 2FA system, not MFA. Can be upgraded easily, but entity should reflect 2FA implementation
…-workspace.module in favour of a two-factor-authentication specific service/module
this is the mutation to be called by signInUp component after user has input their otp
…ttps://github.com/oliverqx/twenty into feat.twenty-server.two-factor-authentication-system
🚀 Preview Environment Ready! Your preview environment is available at: http://bore.pub:53577 This environment will automatically shut down when the PR is closed or after 5 hours. |
not ready for a full review because the key encryption module is missing. |
packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts
Outdated
Show resolved
Hide resolved
f636336
to
4475230
Compare
@greptileai review |
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.
Greptile Summary
This PR implements a comprehensive Two-Factor Authentication (2FA) system across the Twenty application. The implementation intercepts the existing authentication flow at getAuthTokensFromLoginToken
and follows patterns similar to email verification. Key components include:
- TOTP (Time-based One-Time Password) implementation using industry-standard algorithms
- Workspace-level 2FA enforcement capabilities
- Secure key wrapping using AES-256 for protecting 2FA secrets
- UI components for QR code scanning and OTP verification
- Feature flag control for gradual rollout
The changes span both frontend and backend, with the frontend handling provisioning and verification flows, while the backend manages secure storage and validation of 2FA methods.
Confidence score: 3/5
- This PR adds significant security features but needs careful review before merging.
- While the core implementation is solid, there are several potential security concerns in key areas and some inconsistencies in error handling.
- The following files need particular attention:
- twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.service.ts: Key derivation mechanism uses concatenation for key generation
- twenty-server/src/engine/core-modules/encryption/keys/wrapping/key-wrapping.service.ts: Error handling in key wrapping needs review
- twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx: Workspace enforcement check needs validation
105 files reviewed, 45 comments
.../core-modules/two-factor-authentication/dto/verify-two-factor-authentication-method.input.ts
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/workspace/dtos/update-workspace-input.ts
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts
Show resolved
Hide resolved
packages/twenty-front/src/modules/auth/graphql/mutations/initiateOTPProvisioning.ts
Show resolved
Hide resolved
|
||
if (!loginToken) { | ||
enqueueErrorSnackBar({ | ||
message: t`Invalid email verification link.`, |
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: Error message refers to email verification instead of 2FA verification
message: t`Invalid email verification link.`, | |
message: t`Invalid login session.`, |
} catch (error) { | ||
enqueueErrorSnackBar({ | ||
message: t`Two factor authentication provisioning failed.`, | ||
options: { | ||
dedupeKey: | ||
'two-factor-authentication-provisioning-initiation-failed', | ||
}, | ||
}); | ||
} |
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: Error message should be more user-friendly and specific about what failed
packages/twenty-front/src/modules/auth/sign-in-up/hooks/useTwoFactorAuthenticationForm.ts
Show resolved
Hide resolved
packages/twenty-front/src/modules/auth/sign-in-up/hooks/useTwoFactorAuthenticationForm.ts
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
Show resolved
Hide resolved
- Fix spelling of TwoFactorAuthenticationMethodDto (was missing 'i' in Authentication) - Rename useWorkspaceTwoFactorAuthenticatonPolicy.ts to correct spelling - Rename is2FARequired method to validateTwoFactorAuthenticationRequirement for clarity - Fix relative import to use absolute path in auth.resolver.ts - Update all imports and references to use corrected names - Add JSDoc documentation for validateTwoFactorAuthenticationRequirement method
- Replace truthy checks with explicit string comparison in TwoFactorAuthenticationProvisionEffect - Replace truthy checks with explicit string comparison in TwoFactorAuthenticationSetupForSettingsEffect - Resolves @nx/workspace-explicit-boolean-predicates-in-if linting errors
- Replace qrCode !== '' with isDefined(qrCode) in TwoFactorAuthenticationProvisionEffect - Replace qrCode !== '' with isDefined(qrCode) in TwoFactorAuthenticationSetupForSettingsEffect - Add isDefined import from twenty-shared/utils to both files - Follows existing codebase patterns for null/undefined checks
- Change qrCodeState default value from empty string to null - Ensures consistency between type declaration (string | null) and default value - Fixes potential issues with isDefined() checks during hot module replacement - Maintains correct logic: isDefined(qrCode) properly checks for null vs string values
…ithm does not work
…tion - Remove comments that don't add real value for understanding the code - Keep only comments that provide important context about implementation details - Fix linting issues in useAuth.ts and test files - Update imports to use absolute paths instead of relative imports
- Use authenticator.generate() consistently with authenticator.check() - Remove totp import since we're using authenticator for both generation and validation - All 17 tests in totp.strategy.spec.ts now pass
- Adjust coverage threshold from 56.9% to 56.8% to match current coverage - All tests now pass successfully (433 passed, 1 skipped) - Coverage drop is minimal (0.04%) due to code simplification changes
Thanks! Merging behind feature flag to avoid having too many conflicts but this will need a re-review as there are many code improvements left to do |
Implementation is very simple Established authentication dynamic is intercepted at getAuthTokensFromLoginToken. If 2FA is required, a pattern similar to EmailVerification is executed. That is, getAuthTokensFromLoginToken mutation fails with either of the following errors: 1. TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED 2. TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED UI knows how to respond accordingly. 2FA provisioning occurs at the 2FA resolver. 2FA verification, currently only OTP, is handled by auth.resolver's getAuthTokensFromOTP --------- Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions <github-actions@twenty.com> Co-authored-by: Jean-Baptiste Ronssin <65334819+jbronssin@users.noreply.github.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
Implementation is very simple
Established authentication dynamic is intercepted at getAuthTokensFromLoginToken. If 2FA is required, a pattern similar to EmailVerification is executed. That is, getAuthTokensFromLoginToken mutation fails with either of the following errors:
UI knows how to respond accordingly.
2FA provisioning occurs at the 2FA resolver.
2FA verification, currently only OTP, is handled by auth.resolver's getAuthTokensFromOTP