feat: implement password setup link for tenant registration
When admin approves tenant registration: - Owner account created in tenant schema (in addition to admin@localhost) - Owner assigned 'owner' role with full permissions - Password setup email sent with secure 7-day token link - Owner sets password via link and auto-logs into their shop Key changes: - Added password_setup_token fields to TenantRegistration model - Created tenants/services.py with formatted email service - Modified _approve_registration to create owner account - Added password_setup_confirm view with token validation - Created password setup template and URL route - Added admin action to resend password setup emails Security: - Token expires after 7 days - Password not transmitted in email (secure setup link) - Owner account inactive until password set - Admin@localhost preserved for system administrator access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -198,4 +198,91 @@ def password_reset_confirm(request, token):
|
||||
messages.error(request, 'Пароли не совпадают.')
|
||||
|
||||
# Отображаем форму смены пароля
|
||||
return render(request, 'accounts/password_reset_confirm.html', {'user': user})
|
||||
return render(request, 'accounts/password_reset_confirm.html', {'user': user})
|
||||
|
||||
|
||||
def password_setup_confirm(request, token):
|
||||
"""
|
||||
Позволить владельцу тенанта установить начальный пароль после одобрения регистрации.
|
||||
Похоже на сброс пароля, но для новых аккаунтов.
|
||||
"""
|
||||
from tenants.models import TenantRegistration
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
# Найти регистрацию по токену
|
||||
try:
|
||||
registration = TenantRegistration.objects.get(
|
||||
password_setup_token=token,
|
||||
status=TenantRegistration.STATUS_APPROVED
|
||||
)
|
||||
except TenantRegistration.DoesNotExist:
|
||||
messages.error(request, 'Ссылка для настройки пароля недействительна.')
|
||||
return redirect('index')
|
||||
|
||||
# Проверить истечение токена (7 дней)
|
||||
if registration.password_setup_token_created_at:
|
||||
expires_at = registration.password_setup_token_created_at + timedelta(days=7)
|
||||
if timezone.now() > expires_at:
|
||||
messages.error(
|
||||
request,
|
||||
'Ссылка для настройки пароля истекла. Пожалуйста, свяжитесь с поддержкой.'
|
||||
)
|
||||
return redirect('index')
|
||||
|
||||
# Получить тенант и пользователя-владельца
|
||||
from django.db import connection
|
||||
tenant = registration.tenant
|
||||
if not tenant:
|
||||
messages.error(request, 'Тенант не найден.')
|
||||
return redirect('index')
|
||||
|
||||
# Переключиться на схему тенанта чтобы найти владельца
|
||||
connection.set_tenant(tenant)
|
||||
try:
|
||||
User = get_user_model()
|
||||
owner = User.objects.get(email=registration.owner_email)
|
||||
except User.DoesNotExist:
|
||||
connection.set_schema_to_public()
|
||||
messages.error(request, 'Пользователь не найден.')
|
||||
return redirect('index')
|
||||
|
||||
# Обработать POST - установить пароль
|
||||
if request.method == 'POST':
|
||||
password1 = request.POST.get('password1')
|
||||
password2 = request.POST.get('password2')
|
||||
|
||||
if password1 and password2 and password1 == password2:
|
||||
# Установить пароль и активировать аккаунт
|
||||
owner.set_password(password1)
|
||||
owner.is_active = True
|
||||
owner.save()
|
||||
|
||||
# Очистить токен
|
||||
connection.set_schema_to_public()
|
||||
registration.password_setup_token = None
|
||||
registration.password_setup_token_created_at = None
|
||||
registration.save()
|
||||
|
||||
# Автоматический вход
|
||||
connection.set_tenant(tenant)
|
||||
login(request, owner, backend='django.contrib.auth.backends.ModelBackend')
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f'Пароль успешно установлен! Добро пожаловать в {tenant.name}!'
|
||||
)
|
||||
|
||||
# Перенаправить на домен тенанта
|
||||
tenant_url = f'http://{tenant.schema_name}.localhost:8000/'
|
||||
return redirect(tenant_url)
|
||||
else:
|
||||
messages.error(request, 'Пароли не совпадают.')
|
||||
|
||||
connection.set_schema_to_public()
|
||||
|
||||
# Отрисовать форму установки пароля
|
||||
return render(request, 'accounts/password_setup_confirm.html', {
|
||||
'registration': registration,
|
||||
'tenant': tenant
|
||||
})
|
||||
Reference in New Issue
Block a user