mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-09-16 21:55:37 -05:00
Initial work on views
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
from allauth.mfa.adapter import get_adapter as get_mfa_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import Permission
|
||||
@@ -130,6 +131,11 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||
read_only=True,
|
||||
source="socialaccount_set",
|
||||
)
|
||||
is_mfa_enabled = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_mfa_enabled(self, user: User):
|
||||
mfa_adapter = get_mfa_adapter()
|
||||
return mfa_adapter.is_mfa_enabled(user)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
@@ -141,6 +147,7 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||
"auth_token",
|
||||
"social_accounts",
|
||||
"has_usable_password",
|
||||
"is_mfa_enabled",
|
||||
)
|
||||
|
||||
|
||||
|
@@ -459,6 +459,8 @@ SOCIALACCOUNT_PROVIDERS = json.loads(
|
||||
os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
|
||||
)
|
||||
|
||||
MFA_TOTP_ISSUER = "Paperless-ngx"
|
||||
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = "[Paperless-ngx] "
|
||||
|
||||
DISABLE_REGULAR_LOGIN = __get_boolean("PAPERLESS_DISABLE_REGULAR_LOGIN")
|
||||
|
@@ -54,6 +54,7 @@ from paperless.views import GenerateAuthTokenView
|
||||
from paperless.views import GroupViewSet
|
||||
from paperless.views import ProfileView
|
||||
from paperless.views import SocialAccountProvidersView
|
||||
from paperless.views import TOTPActivateView
|
||||
from paperless.views import UserViewSet
|
||||
from paperless_mail.views import MailAccountTestView
|
||||
from paperless_mail.views import MailAccountViewSet
|
||||
@@ -157,8 +158,21 @@ urlpatterns = [
|
||||
),
|
||||
re_path(
|
||||
"^profile/",
|
||||
ProfileView.as_view(),
|
||||
name="profile_view",
|
||||
include(
|
||||
[
|
||||
re_path(
|
||||
"^$",
|
||||
ProfileView.as_view(),
|
||||
name="profile_view",
|
||||
),
|
||||
path(
|
||||
"totp_activate/",
|
||||
TOTPActivateView.as_view(),
|
||||
name="activate",
|
||||
),
|
||||
# TODO: remove allauth urls?
|
||||
],
|
||||
),
|
||||
),
|
||||
re_path(
|
||||
"^status/",
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from allauth.mfa import signals
|
||||
from allauth.mfa.adapter import get_adapter as get_mfa_adapter
|
||||
from allauth.mfa.base.internal.flows import delete_and_cleanup
|
||||
from allauth.mfa.models import Authenticator
|
||||
from allauth.mfa.recovery_codes.internal.flows import auto_generate_recovery_codes
|
||||
from allauth.mfa.totp.internal import auth as totp_auth
|
||||
from allauth.socialaccount.adapter import get_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.contrib.auth.models import Group
|
||||
@@ -145,6 +151,75 @@ class ProfileView(GenericAPIView):
|
||||
return Response(serializer.to_representation(user))
|
||||
|
||||
|
||||
class TOTPActivateView(GenericAPIView):
|
||||
"""
|
||||
TOTP views
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user = self.request.user
|
||||
mfa_adapter = get_mfa_adapter()
|
||||
secret = totp_auth.get_totp_secret(regenerate=True)
|
||||
url = mfa_adapter.build_totp_url(user, secret)
|
||||
svg = mfa_adapter.build_totp_svg(url)
|
||||
return Response(
|
||||
{
|
||||
"url": url,
|
||||
"qr_svg": svg,
|
||||
"secret": secret,
|
||||
},
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
valid = totp_auth.validate_totp_code(
|
||||
request.data["secret"],
|
||||
request.data["code"],
|
||||
)
|
||||
recovery_codes = None
|
||||
if valid:
|
||||
# from allauth.mfa.totp.internal.flows activate_totp
|
||||
auth = totp_auth.TOTP.activate(
|
||||
request.user,
|
||||
request.data["secret"],
|
||||
).instance
|
||||
signals.authenticator_added.send(
|
||||
sender=Authenticator,
|
||||
request=request,
|
||||
user=request.user,
|
||||
authenticator=auth,
|
||||
)
|
||||
# adapter = get_adapter()
|
||||
# adapter.add_message(request, messages.SUCCESS, "mfa/messages/totp_activated.txt")
|
||||
# adapter.send_notification_mail("mfa/email/totp_activated", request.user)
|
||||
rc_auth: Authenticator = auto_generate_recovery_codes(request)
|
||||
if rc_auth:
|
||||
recovery_codes = rc_auth.wrap().get_unused_codes()
|
||||
return Response(
|
||||
{
|
||||
"success": valid,
|
||||
"recovery_codes": recovery_codes,
|
||||
},
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
user = self.request.user
|
||||
try:
|
||||
# from allauth.mfa.totp.internal.flows deactivate_totp
|
||||
authenticator = Authenticator.objects.filter(
|
||||
user=user,
|
||||
type=Authenticator.Type.TOTP,
|
||||
).first()
|
||||
delete_and_cleanup(request, authenticator)
|
||||
# adapter = get_account_adapter(request)
|
||||
# adapter.add_message(request, messages.SUCCESS, "mfa/messages/totp_deactivated.txt")
|
||||
# adapter.send_notification_mail("mfa/email/totp_deactivated", request.user)
|
||||
return Response(True)
|
||||
except Authenticator.DoesNotExist:
|
||||
return HttpResponseBadRequest("TOTP not found")
|
||||
|
||||
|
||||
class GenerateAuthTokenView(GenericAPIView):
|
||||
"""
|
||||
Generates (or re-generates) an auth token, requires a logged in user
|
||||
|
Reference in New Issue
Block a user