feat(users): automatically send the set-password email on user creation
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
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
Admin used to have to click 'Send Email' separately after creating a passwordless user. Now the first set-password email is sent right away as part of creation — best-effort: if the webhook isn't configured or the send fails, the user is still created successfully, and the existing 'Send Email' button remains available to retry with proper error surfacing.
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
"""User management router (admin only)."""
|
"""User management router (admin only)."""
|
||||||
|
|
||||||
|
# Import logging
|
||||||
|
import logging
|
||||||
|
|
||||||
# Import uuid
|
# Import uuid
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -47,6 +50,8 @@ from app.services.password_setup_service import send_password_setup_email
|
|||||||
# Assign router = APIRouter(prefix="/users", tags=["users"])
|
# Assign router = APIRouter(prefix="/users", tags=["users"])
|
||||||
router = APIRouter(prefix="/users", tags=["users"])
|
router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# PATCH /users/me/preferences — update current user preferences
|
# PATCH /users/me/preferences — update current user preferences
|
||||||
@@ -187,6 +192,17 @@ def create_user_route(
|
|||||||
# Reload ORM object attributes from the database
|
# Reload ORM object attributes from the database
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
|
|
||||||
|
# Send the set-password email right away — best-effort. If the webhook
|
||||||
|
# isn't configured or rejects the request, the user is still created
|
||||||
|
# successfully; the admin can retry via the "Send Email" button, which
|
||||||
|
# surfaces the failure properly (unlike this automatic first attempt).
|
||||||
|
try:
|
||||||
|
with UnitOfWork(db) as uow:
|
||||||
|
send_password_setup_email(db, user)
|
||||||
|
uow.commit()
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Automatic set-password email failed for new user %s", user.id, exc_info=True)
|
||||||
|
|
||||||
from app.services.notification_service import notify_roles_by_email
|
from app.services.notification_service import notify_roles_by_email
|
||||||
notify_roles_by_email(
|
notify_roles_by_email(
|
||||||
db, roles=["admin"],
|
db, roles=["admin"],
|
||||||
|
|||||||
@@ -18,6 +18,41 @@ def new_user_id(api, auth_headers):
|
|||||||
return resp.json()["id"]
|
return resp.json()["id"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_user_automatically_sends_set_password_email(api, db, auth_headers):
|
||||||
|
api(
|
||||||
|
"patch", "/api/v1/system/email-webhook-config", auth_headers,
|
||||||
|
json={"url": "https://example.com/power-automate-hook"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("app.services.webhook_email_service.requests.post") as mock_post:
|
||||||
|
resp = api(
|
||||||
|
"post", "/api/v1/users", auth_headers,
|
||||||
|
json={"full_name": "Auto Send User", "email": "autosend@test.com", "role": "viewer"},
|
||||||
|
)
|
||||||
|
assert resp.status_code == 201, resp.text
|
||||||
|
mock_post.assert_called_once()
|
||||||
|
payload = mock_post.call_args.kwargs["json"]
|
||||||
|
assert payload["to"] == "autosend@test.com"
|
||||||
|
assert payload["subject"] == "Set Your Password"
|
||||||
|
assert "token=" in payload["body"]
|
||||||
|
|
||||||
|
from app.models.password_setup_token import PasswordSetupToken
|
||||||
|
token_row = db.query(PasswordSetupToken).filter(
|
||||||
|
PasswordSetupToken.user_id == uuid.UUID(resp.json()["id"]),
|
||||||
|
).first()
|
||||||
|
assert token_row is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_user_succeeds_even_if_auto_send_fails(api, auth_headers):
|
||||||
|
"""No webhook configured yet — the automatic first-email attempt fails,
|
||||||
|
but user creation itself must still succeed."""
|
||||||
|
resp = api(
|
||||||
|
"post", "/api/v1/users", auth_headers,
|
||||||
|
json={"full_name": "No Webhook User", "email": "nowebhook@test.com", "role": "viewer"},
|
||||||
|
)
|
||||||
|
assert resp.status_code == 201, resp.text
|
||||||
|
|
||||||
|
|
||||||
def test_send_password_email_requires_webhook_configured(api, auth_headers, new_user_id):
|
def test_send_password_email_requires_webhook_configured(api, auth_headers, new_user_id):
|
||||||
resp = api("post", f"/api/v1/users/{new_user_id}/send-password-email", auth_headers)
|
resp = api("post", f"/api/v1/users/{new_user_id}/send-password-email", auth_headers)
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
|
|||||||
Reference in New Issue
Block a user