snapshot: modularize workflow views by domain
This commit is contained in:
211
backend/workflows/account_views.py
Normal file
211
backend/workflows/account_views.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model, login as auth_login
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from .branding import get_branding_email_copy
|
||||
from .forms import AccountAvatarForm, AccountDetailsForm, AccountNotificationPreferencesForm, AccountTOTPDisableForm, AccountTOTPEnableForm, AccountTOTPRegenerateRecoveryCodesForm, AppLoginForm, AppTOTPChallengeForm
|
||||
from .models import UserProfile
|
||||
from .roles import get_user_role_label
|
||||
from .totp import build_otpauth_uri, generate_recovery_codes, generate_totp_secret
|
||||
|
||||
|
||||
def login_page_impl(request):
|
||||
if getattr(request.user, 'is_authenticated', False):
|
||||
return redirect('home')
|
||||
|
||||
next_target = (request.POST.get('next') or request.GET.get('next') or '').strip()
|
||||
form = AppLoginForm(request=request, data=request.POST or None)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
user = form.get_user()
|
||||
profile, _ = UserProfile.objects.get_or_create(user=user)
|
||||
safe_next = next_target if next_target.startswith('/') else ''
|
||||
if profile.totp_enabled:
|
||||
request.session['login_pending_user_id'] = user.pk
|
||||
request.session['login_pending_backend'] = getattr(user, 'backend', '')
|
||||
request.session['login_pending_next'] = safe_next
|
||||
return redirect('login_totp')
|
||||
|
||||
auth_login(request, user, backend=getattr(user, 'backend', None))
|
||||
now_ts = int(timezone.now().timestamp())
|
||||
request.session['auth_fresh_ts'] = now_ts
|
||||
request.session['last_activity_ts'] = now_ts
|
||||
return redirect(safe_next or reverse('home'))
|
||||
|
||||
request.session.pop('login_pending_user_id', None)
|
||||
request.session.pop('login_pending_backend', None)
|
||||
request.session.pop('login_pending_next', None)
|
||||
return render(
|
||||
request,
|
||||
'workflows/auth/login.html',
|
||||
{
|
||||
'form': form,
|
||||
'next': next_target,
|
||||
'login_step': 'password',
|
||||
},
|
||||
)
|
||||
|
||||
def login_totp_page_impl(request):
|
||||
if getattr(request.user, 'is_authenticated', False):
|
||||
return redirect('home')
|
||||
|
||||
pending_user_id = request.session.get('login_pending_user_id')
|
||||
backend_path = (request.session.get('login_pending_backend') or '').strip()
|
||||
next_target = (request.session.get('login_pending_next') or '').strip()
|
||||
if not pending_user_id:
|
||||
return redirect('login')
|
||||
|
||||
user = get_object_or_404(get_user_model(), pk=pending_user_id)
|
||||
profile, _ = UserProfile.objects.get_or_create(user=user)
|
||||
if not profile.totp_enabled:
|
||||
request.session.pop('login_pending_user_id', None)
|
||||
request.session.pop('login_pending_backend', None)
|
||||
request.session.pop('login_pending_next', None)
|
||||
return redirect('login')
|
||||
|
||||
show_recovery = request.method == 'POST' and bool((request.POST.get('recovery_code') or '').strip())
|
||||
form = AppTOTPChallengeForm(data=request.POST or None, profile=profile)
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
auth_login(request, user, backend=backend_path or 'django.contrib.auth.backends.ModelBackend')
|
||||
request.session.pop('login_pending_user_id', None)
|
||||
request.session.pop('login_pending_backend', None)
|
||||
request.session.pop('login_pending_next', None)
|
||||
now_ts = int(timezone.now().timestamp())
|
||||
request.session['auth_fresh_ts'] = now_ts
|
||||
request.session['last_activity_ts'] = now_ts
|
||||
return redirect(next_target if next_target.startswith('/') else reverse('home'))
|
||||
|
||||
return render(
|
||||
request,
|
||||
'workflows/auth/login.html',
|
||||
{
|
||||
'form': form,
|
||||
'next': next_target,
|
||||
'login_step': 'totp',
|
||||
'login_totp_user': user,
|
||||
'show_recovery_code': show_recovery,
|
||||
},
|
||||
)
|
||||
|
||||
def account_profile_page_impl(request):
|
||||
session_secret_key = 'account_totp_pending_secret'
|
||||
session_codes_key = 'account_totp_recovery_codes'
|
||||
profile, created = UserProfile.objects.get_or_create(user=request.user)
|
||||
recovery_codes = request.session.pop(session_codes_key, [])
|
||||
pending_totp_secret = request.session.get(session_secret_key) or ''
|
||||
if profile.totp_enabled:
|
||||
pending_totp_secret = ''
|
||||
request.session.pop(session_secret_key, None)
|
||||
elif not pending_totp_secret:
|
||||
pending_totp_secret = generate_totp_secret()
|
||||
request.session[session_secret_key] = pending_totp_secret
|
||||
|
||||
avatar_form = AccountAvatarForm(instance=profile)
|
||||
details_form = AccountDetailsForm(user=request.user, profile=profile)
|
||||
notification_preferences_form = AccountNotificationPreferencesForm(profile=profile, user=request.user)
|
||||
totp_enable_form = AccountTOTPEnableForm(user=request.user, secret=pending_totp_secret)
|
||||
totp_disable_form = AccountTOTPDisableForm(user=request.user, profile=profile)
|
||||
totp_regenerate_form = AccountTOTPRegenerateRecoveryCodesForm(user=request.user, profile=profile)
|
||||
account_edit_open = False
|
||||
notifications_edit_open = False
|
||||
totp_edit_open = False
|
||||
if request.method == 'POST':
|
||||
form_kind = (request.POST.get('account_form') or '').strip()
|
||||
if form_kind == 'avatar':
|
||||
avatar_form = AccountAvatarForm(request.POST, request.FILES, instance=profile)
|
||||
if avatar_form.is_valid():
|
||||
avatar_form.save()
|
||||
messages.success(request, _('Profilbild gespeichert.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('Profilbild konnte nicht gespeichert werden.'))
|
||||
elif form_kind == 'details':
|
||||
account_edit_open = True
|
||||
details_form = AccountDetailsForm(request.POST, user=request.user, profile=profile)
|
||||
if details_form.is_valid():
|
||||
details_form.save()
|
||||
messages.success(request, _('Profildaten gespeichert.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('Profildaten konnten nicht gespeichert werden.'))
|
||||
elif form_kind == 'notification_preferences':
|
||||
notifications_edit_open = True
|
||||
notification_preferences_form = AccountNotificationPreferencesForm(request.POST, profile=profile, user=request.user)
|
||||
if notification_preferences_form.is_valid():
|
||||
notification_preferences_form.save()
|
||||
messages.success(request, _('Benachrichtigungseinstellungen gespeichert.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('Benachrichtigungseinstellungen konnten nicht gespeichert werden.'))
|
||||
elif form_kind == 'totp_enable':
|
||||
totp_edit_open = True
|
||||
totp_enable_form = AccountTOTPEnableForm(request.POST, user=request.user, secret=pending_totp_secret)
|
||||
if totp_enable_form.is_valid():
|
||||
recovery_codes = generate_recovery_codes()
|
||||
profile.enable_totp(pending_totp_secret, recovery_codes)
|
||||
request.session[session_codes_key] = recovery_codes
|
||||
request.session.pop(session_secret_key, None)
|
||||
messages.success(request, _('TOTP wurde aktiviert.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('TOTP konnte nicht aktiviert werden.'))
|
||||
elif form_kind == 'totp_disable':
|
||||
totp_edit_open = True
|
||||
totp_disable_form = AccountTOTPDisableForm(request.POST, user=request.user, profile=profile)
|
||||
if totp_disable_form.is_valid():
|
||||
profile.disable_totp()
|
||||
request.session.pop(session_secret_key, None)
|
||||
messages.success(request, _('TOTP wurde deaktiviert.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('TOTP konnte nicht deaktiviert werden.'))
|
||||
elif form_kind == 'totp_regenerate_codes':
|
||||
totp_edit_open = True
|
||||
totp_regenerate_form = AccountTOTPRegenerateRecoveryCodesForm(request.POST, user=request.user, profile=profile)
|
||||
if totp_regenerate_form.is_valid():
|
||||
recovery_codes = generate_recovery_codes()
|
||||
profile.set_recovery_codes(recovery_codes)
|
||||
profile.save(update_fields=['totp_recovery_codes', 'updated_at'])
|
||||
request.session[session_codes_key] = recovery_codes
|
||||
messages.success(request, _('Recovery-Codes wurden neu erzeugt.'))
|
||||
return redirect('account_profile_page')
|
||||
messages.error(request, _('Recovery-Codes konnten nicht neu erzeugt werden.'))
|
||||
|
||||
branding_context = get_branding_email_copy()
|
||||
totp_account_name = (request.user.email or request.user.username or '').strip()
|
||||
totp_issuer = (branding_context.get('portal_title') or branding_context.get('company_name') or 'Workdock').strip()
|
||||
totp_otpauth_uri = '' if profile.totp_enabled else build_otpauth_uri(pending_totp_secret, account_name=totp_account_name, issuer=totp_issuer)
|
||||
totp_qr_svg = ''
|
||||
if totp_otpauth_uri:
|
||||
try:
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
|
||||
qr_image = qrcode.make(totp_otpauth_uri, image_factory=qrcode.image.svg.SvgPathImage)
|
||||
stream = BytesIO()
|
||||
qr_image.save(stream)
|
||||
totp_qr_svg = stream.getvalue().decode('utf-8')
|
||||
except Exception:
|
||||
totp_qr_svg = ''
|
||||
return render(
|
||||
request,
|
||||
'workflows/account_profile.html',
|
||||
{
|
||||
'account_user': request.user,
|
||||
'account_user_profile': profile,
|
||||
'avatar_form': avatar_form,
|
||||
'details_form': details_form,
|
||||
'notification_preferences_form': notification_preferences_form,
|
||||
'notification_preference_groups': notification_preferences_form.grouped_fields(),
|
||||
'totp_enable_form': totp_enable_form,
|
||||
'totp_disable_form': totp_disable_form,
|
||||
'totp_regenerate_form': totp_regenerate_form,
|
||||
'account_edit_open': account_edit_open,
|
||||
'notifications_edit_open': notifications_edit_open,
|
||||
'totp_edit_open': totp_edit_open,
|
||||
'role_label': get_user_role_label(request.user),
|
||||
'totp_pending_secret': pending_totp_secret,
|
||||
'totp_otpauth_uri': totp_otpauth_uri,
|
||||
'totp_qr_svg': totp_qr_svg,
|
||||
'totp_recovery_codes': recovery_codes,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user