feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -17,6 +17,7 @@ from rest_framework.response import Response
try:
import qrcode
HAS_QRCODE = True
except ImportError:
HAS_QRCODE = False
@@ -59,12 +60,14 @@ def get_mfa_status(request):
except Authenticator.DoesNotExist:
pass
return Response({
"mfa_enabled": totp_enabled,
"totp_enabled": totp_enabled,
"recovery_codes_enabled": recovery_enabled,
"recovery_codes_count": recovery_count,
})
return Response(
{
"mfa_enabled": totp_enabled,
"totp_enabled": totp_enabled,
"recovery_codes_enabled": recovery_enabled,
"recovery_codes_count": recovery_count,
}
)
@extend_schema(
@@ -110,11 +113,13 @@ def setup_totp(request):
# Store secret in session for later verification
request.session["pending_totp_secret"] = secret
return Response({
"secret": secret,
"provisioning_uri": uri,
"qr_code_base64": qr_code_base64,
})
return Response(
{
"secret": secret,
"provisioning_uri": uri,
"qr_code_base64": qr_code_base64,
}
)
@extend_schema(
@@ -138,8 +143,7 @@ def setup_totp(request):
200: {
"description": "TOTP activated successfully",
"example": {
"success": True,
"message": "Two-factor authentication enabled",
"detail": "Two-factor authentication enabled",
"recovery_codes": ["ABCD1234", "EFGH5678"],
},
},
@@ -160,7 +164,7 @@ def activate_totp(request):
if not code:
return Response(
{"success": False, "error": "Verification code is required"},
{"detail": "Verification code is required"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -168,21 +172,21 @@ def activate_totp(request):
secret = request.session.get("pending_totp_secret")
if not secret:
return Response(
{"success": False, "error": "No pending TOTP setup. Please start setup again."},
{"detail": "No pending TOTP setup. Please start setup again."},
status=status.HTTP_400_BAD_REQUEST,
)
# Verify the code
if not totp_auth.validate_totp_code(secret, code):
return Response(
{"success": False, "error": "Invalid verification code"},
{"detail": "Invalid verification code"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if already has TOTP
if Authenticator.objects.filter(user=user, type=Authenticator.Type.TOTP).exists():
return Response(
{"success": False, "error": "TOTP is already enabled"},
{"detail": "TOTP is already enabled"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -204,11 +208,12 @@ def activate_totp(request):
# Clear session
del request.session["pending_totp_secret"]
return Response({
"success": True,
"message": "Two-factor authentication enabled",
"recovery_codes": codes,
})
return Response(
{
"detail": "Two-factor authentication enabled",
"recovery_codes": codes,
}
)
@extend_schema(
@@ -230,7 +235,7 @@ def activate_totp(request):
responses={
200: {
"description": "TOTP disabled",
"example": {"success": True, "message": "Two-factor authentication disabled"},
"example": {"detail": "Two-factor authentication disabled"},
},
400: {"description": "Invalid password or MFA not enabled"},
},
@@ -248,26 +253,26 @@ def deactivate_totp(request):
# Verify password
if not user.check_password(password):
return Response(
{"success": False, "error": "Invalid password"},
{"detail": "Invalid password"},
status=status.HTTP_400_BAD_REQUEST,
)
# Remove TOTP and recovery codes
deleted_count, _ = Authenticator.objects.filter(
user=user,
type__in=[Authenticator.Type.TOTP, Authenticator.Type.RECOVERY_CODES]
user=user, type__in=[Authenticator.Type.TOTP, Authenticator.Type.RECOVERY_CODES]
).delete()
if deleted_count == 0:
return Response(
{"success": False, "error": "Two-factor authentication is not enabled"},
{"detail": "Two-factor authentication is not enabled"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response({
"success": True,
"message": "Two-factor authentication disabled",
})
return Response(
{
"detail": "Two-factor authentication disabled",
}
)
@extend_schema(
@@ -277,9 +282,7 @@ def deactivate_totp(request):
request={
"application/json": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "6-digit TOTP code"}
},
"properties": {"code": {"type": "string", "description": "6-digit TOTP code"}},
"required": ["code"],
}
},
@@ -301,7 +304,7 @@ def verify_totp(request):
if not code:
return Response(
{"success": False, "error": "Verification code is required"},
{"detail": "Verification code is required"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -313,12 +316,12 @@ def verify_totp(request):
return Response({"success": True})
else:
return Response(
{"success": False, "error": "Invalid verification code"},
{"detail": "Invalid verification code"},
status=status.HTTP_400_BAD_REQUEST,
)
except Authenticator.DoesNotExist:
return Response(
{"success": False, "error": "TOTP is not enabled"},
{"detail": "TOTP is not enabled"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -330,9 +333,7 @@ def verify_totp(request):
request={
"application/json": {
"type": "object",
"properties": {
"password": {"type": "string", "description": "Current password"}
},
"properties": {"password": {"type": "string", "description": "Current password"}},
"required": ["password"],
}
},
@@ -358,14 +359,14 @@ def regenerate_recovery_codes(request):
# Verify password
if not user.check_password(password):
return Response(
{"success": False, "error": "Invalid password"},
{"detail": "Invalid password"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if TOTP is enabled
if not Authenticator.objects.filter(user=user, type=Authenticator.Type.TOTP).exists():
return Response(
{"success": False, "error": "Two-factor authentication is not enabled"},
{"detail": "Two-factor authentication is not enabled"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -379,7 +380,9 @@ def regenerate_recovery_codes(request):
defaults={"data": {"codes": codes}},
)
return Response({
"success": True,
"recovery_codes": codes,
})
return Response(
{
"success": True,
"recovery_codes": codes,
}
)