Skip to content

Conversation

oliverqx
Copy link
Contributor

@oliverqx oliverqx commented Jul 9, 2025

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

oliverqx and others added 20 commits July 9, 2025 12:24
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
Copy link
Contributor

github-actions bot commented Jul 9, 2025

🚀 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.

@oliverqx oliverqx marked this pull request as draft July 9, 2025 20:05
@oliverqx
Copy link
Contributor Author

oliverqx commented Jul 9, 2025

not ready for a full review because the key encryption module is missing.

@FelixMalfait FelixMalfait self-assigned this Jul 9, 2025
@FelixMalfait FelixMalfait force-pushed the feat.twenty-server.two-factor-authentication-system branch from f636336 to 4475230 Compare July 21, 2025 11:03
@FelixMalfait FelixMalfait marked this pull request as ready for review July 22, 2025 14:28
@FelixMalfait
Copy link
Member

@greptileai review

Copy link
Contributor

@greptile-apps greptile-apps bot left a 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

  1. This PR adds significant security features but needs careful review before merging.
  2. While the core implementation is solid, there are several potential security concerns in key areas and some inconsistencies in error handling.
  3. 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

Edit Code Review Bot Settings | Greptile


if (!loginToken) {
enqueueErrorSnackBar({
message: t`Invalid email verification link.`,
Copy link
Contributor

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

Suggested change
message: t`Invalid email verification link.`,
message: t`Invalid login session.`,

Comment on lines 42 to 50
} catch (error) {
enqueueErrorSnackBar({
message: t`Two factor authentication provisioning failed.`,
options: {
dedupeKey:
'two-factor-authentication-provisioning-initiation-failed',
},
});
}
Copy link
Contributor

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

FelixMalfait and others added 13 commits July 22, 2025 18:33
- 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
…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
@FelixMalfait
Copy link
Member

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

@FelixMalfait FelixMalfait merged commit 4d3124f into twentyhq:main Jul 23, 2025
56 checks passed
@oliverqx oliverqx deleted the feat.twenty-server.two-factor-authentication-system branch July 24, 2025 11:44
prastoin pushed a commit that referenced this pull request Jul 24, 2025
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants