Files
ACORE/apps/employees/views_password.py
T
lokses 981a481e3f employees: массовая выдача ссылок первичной установки пароля + MM-синк
Админ-страница «Ссылки на установку пароля» (Сотрудники → действия):
таблица всех активных сотрудников со статусом (пароль установлен / MM
привязан / активная ссылка), кнопки «создать всем без пароля», создать/
обновить/отозвать по одному, экспорт CSV. Ссылки одноразовые, TTL 30 дней.

При активации по ссылке, помимо пароля портала, теперь синхронно ставится
тот же пароль в Mattermost: аккаунт связывается по email/ID или создаётся
(первичная связь). Ошибка MM не отменяет установку пароля портала.

- services/password_invite: bulk_create_invites_for_pending,
  active_invites_by_employee, revoke_active_invites (BULK_TTL_DAYS=30)
- mattermost sync_service: link_and_set_password (ok|disabled|error)
- views_password: MM-синк после commit (вне транзакции, best-effort)
- EmployeeAdmin: action password-invites + шаблон
- tests: 6 новых (bulk, revoke, MM-линк ok/disabled, активация, admin+CSV)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 23:28:31 +03:00

80 lines
3.0 KiB
Python

"""Публичная установка пароля по приглашению."""
from django.contrib.auth import password_validation
from django.core.exceptions import ValidationError
from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from apps.employees.services.password_invite import (
ad_sync_hint,
can_offer_ad_password_sync,
complete_password_setup,
get_active_invite,
)
def _validate_password_pair(password: str, confirm: str) -> list[str]:
errors = []
if not password:
errors.append('Введіть пароль.')
if password != confirm:
errors.append('Паролі не збігаються.')
if password:
try:
password_validation.validate_password(password)
except ValidationError as exc:
errors.extend(exc.messages)
return errors
@require_http_methods(['GET', 'POST'])
def password_setup_view(request, token):
invite = get_active_invite(token)
if invite is None:
return render(request, 'password-setup.html', {
'invalid': True,
}, status=404)
employee = invite.employee
show_ad = can_offer_ad_password_sync(employee)
hint = ad_sync_hint(employee)
if request.method == 'POST':
password = request.POST.get('password', '')
confirm = request.POST.get('password_confirm', '')
sync_ad = show_ad and request.POST.get('sync_ad') == '1'
errors = _validate_password_pair(password, confirm)
if not errors:
try:
complete_password_setup(invite, password, sync_ad=sync_ad)
except ValueError:
return render(request, 'password-setup.html', {
'invalid': True,
}, status=410)
# Пароль портала установлен и зафиксирован (транзакция закрыта) —
# синхронно привязываем MM-аккаунт и ставим тот же пароль. Ошибка
# MM не должна отменять установку пароля портала.
try:
from apps.mattermost_connector.services.sync_service import (
link_and_set_password,
)
link_and_set_password(employee, password)
except Exception: # noqa: BLE001 — best-effort, детали уже в логах сервиса
pass
return redirect(f'/login/?setup=1&user={employee.username}')
return render(request, 'password-setup.html', {
'employee': employee,
'show_ad_sync': show_ad,
'ad_sync_hint': hint,
'errors': errors,
'sync_ad_checked': sync_ad,
})
return render(request, 'password-setup.html', {
'employee': employee,
'show_ad_sync': show_ad,
'ad_sync_hint': hint,
'expires_at': invite.expires_at,
})