feat(users): passwordless user creation via emailed set-password link, display full name instead of username
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

Admins now create users with a name + email (no password) and role;
a Send Email action (per-user, also reusable for resets) issues a
one-time token and POSTs it to an admin-configurable webhook URL —
intended for a Power Automate flow that delivers the actual email.
The old admin-sets-the-password flow is kept server-side
(LegacyUserCreateWithPassword) but no longer wired into the UI.

Every user-facing surface (top bar, sidebar, user management, audit
log, assignee pickers, dispute notifications) now shows full_name
instead of username, falling back to username when unset.

Also fixes long attack_procedure/expected_detection/suggested_text
text overflowing its container in the template review panels and
Red/Blue team fields (missing break-words on whitespace-pre-wrap
blocks).
This commit is contained in:
kitos
2026-07-17 16:58:25 +02:00
parent 60ab2e558f
commit 4dea19cae9
36 changed files with 974 additions and 159 deletions
+49 -12
View File
@@ -21,16 +21,24 @@ from app.domain.unit_of_work import UnitOfWork
# Import User from app.models.user
from app.models.user import User
from app.dependencies.auth import get_current_user
from app.schemas.user import UserCreate, UserUpdate, UserOut, UserPreferencesUpdate, UserOperatorOut
from app.schemas.user import (
UserCreate,
UserUpdate,
UserOut,
UserPreferencesUpdate,
UserOperatorOut,
SendPasswordEmailOut,
)
from app.services.audit_service import log_action
# Import from app.services.user_service
from app.services.user_service import (
create_user,
create_user_without_password,
get_user_or_raise,
list_users,
update_user,
)
from app.services.password_setup_service import send_password_setup_email
# Assign router = APIRouter(prefix="/users", tags=["users"])
router = APIRouter(prefix="/users", tags=["users"])
@@ -110,19 +118,17 @@ def create_user_route(
# Entry: current_user
current_user: User = Depends(require_role("admin")),
) -> UserOut:
"""Create a new user. **Requires admin role.**."""
"""Create a new user. **Requires admin role.**.
No password is set — use ``POST /users/{id}/send-password-email``
afterward to let the user set their own via a one-time link.
"""
# Open context manager
with UnitOfWork(db) as uow:
# Assign user = create_user(
user = create_user(
user = create_user_without_password(
db,
# Keyword argument: username
username=payload.username,
# Keyword argument: email
full_name=payload.full_name,
email=payload.email,
# Keyword argument: password
password=payload.password,
# Keyword argument: role
role=payload.role,
)
# Call log_action()
@@ -137,7 +143,7 @@ def create_user_route(
# Keyword argument: entity_id
entity_id=user.id,
# Keyword argument: details
details={"username": user.username, "role": user.role},
details={"full_name": user.full_name, "email": user.email, "role": user.role},
)
# Call uow.commit()
uow.commit()
@@ -241,3 +247,34 @@ def update_user_route(
# Return user
return user
# ---------------------------------------------------------------------------
# POST /users/{id}/send-password-email — set-password / reset link
# ---------------------------------------------------------------------------
@router.post("/{user_id}/send-password-email", response_model=SendPasswordEmailOut)
def send_password_email_route(
user_id: uuid.UUID,
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
) -> SendPasswordEmailOut:
"""Email a one-time set-password link to a user. **Requires admin role.**.
Works for both a freshly-created passwordless user and an existing
one whose password needs resetting — same link, same flow either way.
"""
user = get_user_or_raise(db, user_id)
with UnitOfWork(db) as uow:
send_password_setup_email(db, user)
log_action(
db,
user_id=current_user.id,
action="send_password_setup_email",
entity_type="user",
entity_id=user.id,
details={},
)
uow.commit()
return SendPasswordEmailOut(detail=f"Password setup email sent to {user.email}")