feat: support password field in /user/create webhook

When Evotor sends a password in the payload, hash and store it
immediately and set the user to active — skipping the invite flow.
Without a password, behaviour is unchanged (pending status + invite email).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mguschin
2026-05-24 16:57:58 +03:00
parent 74f613a4c3
commit 5a67be2c81

View File

@@ -17,7 +17,7 @@ from fastapi.responses import JSONResponse
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from web.auth.password import verify_password from web.auth.password import hash_password, verify_password
from web.config import settings from web.config import settings
from web.database import get_db from web.database import get_db
from web.models.connections import EvotorConnection from web.models.connections import EvotorConnection
@@ -108,6 +108,7 @@ async def user_create(request: Request, db: Session = Depends(get_db)):
first_name = (body.get("first_name") or body.get("firstName") or custom.get("first_name") or custom.get("firstName") or "").strip() or None first_name = (body.get("first_name") or body.get("firstName") or custom.get("first_name") or custom.get("firstName") or "").strip() or None
last_name = (body.get("second_name") or body.get("last_name") or body.get("lastName") or custom.get("second_name") or custom.get("last_name") or custom.get("lastName") or "").strip() or None last_name = (body.get("second_name") or body.get("last_name") or body.get("lastName") or custom.get("second_name") or custom.get("last_name") or custom.get("lastName") or "").strip() or None
middle_name = (body.get("middle_name") or custom.get("middle_name") or "").strip() or None middle_name = (body.get("middle_name") or custom.get("middle_name") or "").strip() or None
password = (body.get("password") or custom.get("password") or "").strip() or None
# Try to find existing user # Try to find existing user
user: User | None = None user: User | None = None
@@ -135,20 +136,22 @@ async def user_create(request: Request, db: Session = Depends(get_db)):
user.last_name = last_name user.last_name = last_name
if middle_name: if middle_name:
user.middle_name = middle_name user.middle_name = middle_name
if password and not user.password_hash:
user.password_hash = hash_password(password)
if user.status == UserStatusEnum.pending: if user.status == UserStatusEnum.pending:
user.status = UserStatusEnum.active user.status = UserStatusEnum.active
db.flush() db.flush()
else: else:
# Create new pending user # Create new user; active immediately if password provided, else pending
user = User( user = User(
first_name=first_name or "", first_name=first_name or "",
last_name=last_name or "", last_name=last_name or "",
middle_name=middle_name, middle_name=middle_name,
email=email or f"{evotor_user_id}@evotor.invalid", email=email or f"{evotor_user_id}@evotor.invalid",
phone=phone or None, phone=phone or None,
password_hash=None, password_hash=hash_password(password) if password else None,
role=UserRoleEnum.user, role=UserRoleEnum.user,
status=UserStatusEnum.pending, status=UserStatusEnum.active if password else UserStatusEnum.pending,
evotor_user_id=evotor_user_id, evotor_user_id=evotor_user_id,
evotor_meta=body, evotor_meta=body,
created_at=now, created_at=now,
@@ -157,27 +160,28 @@ async def user_create(request: Request, db: Session = Depends(get_db)):
db.add(user) db.add(user)
db.flush() # get user.id db.flush() # get user.id
# Generate invite
invite_token = secrets.token_urlsafe(32)
user.invite_token = invite_token
user.invite_expires = now + timedelta(hours=settings.INVITE_EXPIRE_HOURS)
api_token = _upsert_evotor_connection(db, user.id, evotor_user_id) api_token = _upsert_evotor_connection(db, user.id, evotor_user_id)
db.commit()
# Send invite email if we have a real email address if not password:
if email: # No password provided — send invite link so user can set one
invite_url = f"{settings.BASE_URL}/invite?token={invite_token}" invite_token = secrets.token_urlsafe(32)
html = ( user.invite_token = invite_token
f"<p>Здравствуйте!</p>" user.invite_expires = now + timedelta(hours=settings.INVITE_EXPIRE_HOURS)
f"<p>Вам открыт доступ к ЭВОСИНК. Завершите регистрацию по ссылке:</p>" db.commit()
f'<p><a href="{invite_url}">{invite_url}</a></p>' if email:
f"<p>Ссылка действительна {settings.INVITE_EXPIRE_HOURS} часов.</p>" invite_url = f"{settings.BASE_URL}/invite?token={invite_token}"
) html = (
send_email_task.delay(email, "Приглашение в ЭВОСИНК", html) f"<p>Здравствуйте!</p>"
f"<p>Вам открыт доступ к ЭВОСИНК. Завершите регистрацию по ссылке:</p>"
f'<p><a href="{invite_url}">{invite_url}</a></p>'
f"<p>Ссылка действительна {settings.INVITE_EXPIRE_HOURS} часов.</p>"
)
send_email_task.delay(email, "Приглашение в ЭВОСИНК", html)
else:
logger.info("No email for evotor_user_id=%s, invite URL: %s/invite?token=%s",
evotor_user_id, settings.BASE_URL, invite_token)
else: else:
logger.info("No email for evotor_user_id=%s, invite URL: %s/invite?token=%s", db.commit()
evotor_user_id, settings.BASE_URL, invite_token)
return JSONResponse({"userId": evotor_user_id, "token": api_token}) return JSONResponse({"userId": evotor_user_id, "token": api_token})