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:
@@ -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})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user