-
Notifications
You must be signed in to change notification settings - Fork 345
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
MFA v1 - TOTP #2830
base: develop
Are you sure you want to change the base?
MFA v1 - TOTP #2830
Conversation
… store encrypted secrets
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2830 +/- ##
===========================================
- Coverage 56.24% 56.01% -0.23%
===========================================
Files 215 218 +3
Lines 10231 10404 +173
Branches 1038 1052 +14
===========================================
+ Hits 5754 5828 +74
- Misses 4461 4560 +99
Partials 16 16 ☔ View full report in Codecov by Sentry. |
@@ -18,3 +18,5 @@ BUCKET_ENDPOINT=http://localhost:4566 | |||
BUCKET_EXTERNAL_ENDPOINT=http://localhost:4566 | |||
FILE_UPLOAD_BUCKET=patient-bucket | |||
FACILITY_S3_BUCKET=facility-bucket | |||
|
|||
TOTP_SECRET_ENCRYPTION_KEY=your-encryption-key-here |
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.
We already have a secret key, lets reuse that.
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 can't find one in this https://github.com/ohcnetwork/care/blob/develop/.env.example, by any chance is it django_secret_key
?
care/emr/api/viewsets/totp.py
Outdated
return Response({"backup_codes": backup_codes}) | ||
|
||
@staticmethod | ||
def _check_totp_enabled(mfa_settings: dict): |
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 function should return either true or false and let the implementer write the exception.
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 function should return either true or false and let the implementer write the exception.
Yeah, I thought this was the expected way when writting this method but wrote the exception inside the_check_totp_enabled
as the exception is same for all three methods which are calling this so trying to not repeat the code made me write this way and also this will make understanding the code little tough and also misleading with its method name.
Will fix it 👍
) | ||
|
||
secret = random_base32() | ||
encrypted_secret = encrypt_string(secret) |
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 didn't really understand how encrypting this string has a security advantage, for someone to hack around this, they need access to the database and not the application, but if you have access to the database then we have already lost right?
And the backup codes are not encrypted, that already undoes all this work.
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.
And the backup codes are not encrypted, that already undoes all this work.
Backup codes are hashed and stored
care/care/emr/api/viewsets/totp.py
Line 128 in f46573c
"code": make_password(code), |
So even some get's access to DB. they can't use it
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.
but if you have access to the database then we have already lost right?
Yes
but
If it is plain text they can generate the TOTP codes and if it is encrypted, one cannot generate, and also it will save during accidental exposure
🤔
care/emr/api/viewsets/totp.py
Outdated
user = request.user | ||
|
||
if not user.totp_secret: | ||
return Response( |
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.
raise a validation error
def login(self, request): | ||
request_data = TOTPLoginRequest(**request.data) | ||
|
||
token = RefreshToken(request_data.temp_token) |
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.
Need exception handling around this, if an invalid token is given it will raise an error
if totp.verify(request_data.code): | ||
refresh = RefreshToken.for_user(user) | ||
|
||
try: |
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.
No need to blacklist, you can let this token exist, this token can only be used with an MFA option.
user = User.objects.get(external_id=token["user_id"]) | ||
totp = TOTP(decrypt_string(user.totp_secret)) | ||
|
||
if totp.verify(request_data.code): |
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.
How is the backup code used here? the backup codes are maintained by us or the package?
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.
By us, pyotp
does not provide out of the box
permission_classes=[], | ||
authentication_classes=[], | ||
) | ||
def backup_login(self, request): |
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.
Why cant we just use the same endpoint and check the backup codes if the others dont match?
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.
We can, but pyotp
does not provide out of the box backup_codes support, and it is implemented by us, so keeping both in one endpoint will make the method bulkier 😁 and having separate endpoint provides clear separation
but a lot of shared code between two login
and backup_login
will think whether I can reduce some duplicity of code and will think what will it look like both in single endpoint
Do you have reasons to make it in a single endpoint ?
permission_classes=[], | ||
authentication_classes=[], | ||
) | ||
def login(self, request): |
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.
We need ratelimiting, the token should expire within 5 mins as well.
Proposed Changes
This pull request introduces a new feature for Two-Factor Authentication (2FA) using Time-based One-Time Password (TOTP). The changes include updates to various files to support TOTP setup, verification, and login processes.
New TOTP Feature Implementation:
care/emr/api/viewsets/totp.py
: Added a newTOTPViewSet
class to handle TOTP setup, verification, and login. This includes methods for generating backup codes, encrypting/decrypting TOTP secrets, and handling TOTP-related requests.care/users/migrations/0022_user_mfa_settings_user_totp_secret.py
: Added a migration to includemfa_settings
andtotp_secret
fields in theUser
model.care/users/models.py
: Updated theUser
model to includetotp_secret
andmfa_settings
fields.care/utils/encryption.py
: Added utility functions for encrypting and decrypting strings using Fernet.config/api_router.py
: Registered the newTOTPViewSet
in the API router. [1] [2]Configuration and Dependencies:
.env.example
: AddedTOTP_SECRET_ENCRYPTION_KEY
to the environment variables.Pipfile
: Added thepyotp
library as a dependency for TOTP functionality.config/settings/base.py
: AddedTOTP_SECRET_ENCRYPTION_KEY
to the settings.docker/.local.env
: AddedTOTP_SECRET_ENCRYPTION_KEY
to the local environment variables.docker/.prebuilt.env
: AddedTOTP_SECRET_ENCRYPTION_KEY
to the prebuilt environment variables.Authentication Updates:
config/auth_views.py
: Updated theTokenObtainPairSerializer
andTokenRefreshSerializer
to handle temporary tokens for TOTP verification during login. [1] [2] [3]Associated Issue
TOTP Feature Implementation:
care/emr/api/viewsets/totp.py
: Added a newTOTPViewSet
class with endpoints for TOTP setup, verification, login, disabling, and backup code management.care/emr/tasks/totp.py
: Added tasks to send email notifications when TOTP is enabled or disabled.care/templates/email/totp_enabled.html
: Created an email template for TOTP enabled notifications.care/templates/email/totp_disabled.html
: Created an email template for TOTP disabled notifications.User Model and Migration:
care/users/models.py
: Addedtotp_secret
andmfa_settings
fields to theUser
model.care/users/migrations/0022_user_mfa_settings_user_totp_secret.py
: Created a migration to add the new fields to theUser
model.Encryption Utilities:
care/utils/encryption.py
: Added utility functions for encrypting and decrypting strings using Fernet.Configuration and Environment Variables:
.env.example
,docker/.local.env
,docker/.prebuilt.env
,config/settings/base.py
: AddedTOTP_SECRET_ENCRYPTION_KEY
to environment variables and settings. [1] [2] [3] [4]API Router and Authentication:
config/api_router.py
: Registered the newTOTPViewSet
with the API router. [1] [2]config/auth_views.py
: Updated token serializers to handle temporary tokens for TOTP verification during login. [1] [2] [3]Dependency Updates:
Pipfile
: Addedpyotp
dependency for TOTP functionality.Merge Checklist
/docs
Only PR's with test cases included and passing lint and test pipelines will be reviewed
@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins