Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta, timezone
|
||||
import hashlib
|
||||
import re
|
||||
import secrets
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.security.jwt_tokens import utcnow
|
||||
from app.core.security.passwords import hash_password
|
||||
from app.core.settings import get_settings
|
||||
from app.modules.core.iam.models import User
|
||||
from app.modules.core.iam.password_flows_models import InviteToken, PasswordResetToken
|
||||
|
||||
|
||||
def _hash_token(token: str) -> str:
|
||||
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def validate_password_policy(password: str) -> str | None:
|
||||
s = get_settings()
|
||||
if len(password or "") < s.PASSWORD_MIN_LENGTH:
|
||||
return f"Password must be at least {s.PASSWORD_MIN_LENGTH} characters long."
|
||||
if not re.search(r"[A-Za-z]", password or ""):
|
||||
return "Password must include at least one letter."
|
||||
if not re.search(r"\d", password or ""):
|
||||
return "Password must include at least one number."
|
||||
return None
|
||||
|
||||
|
||||
def issue_invite_token(db: Session, user: User) -> str:
|
||||
plain = secrets.token_urlsafe(32)
|
||||
now = utcnow()
|
||||
token = InviteToken(
|
||||
user_id=user.id,
|
||||
token_hash=_hash_token(plain),
|
||||
created_at_utc=now,
|
||||
expires_at_utc=now + timedelta(hours=get_settings().INVITE_TOKEN_HOURS),
|
||||
used_at_utc=None,
|
||||
)
|
||||
db.add(token)
|
||||
user.must_change_password = True
|
||||
db.commit()
|
||||
return plain
|
||||
|
||||
|
||||
def issue_password_reset_token(db: Session, user: User) -> str:
|
||||
plain = secrets.token_urlsafe(32)
|
||||
now = utcnow()
|
||||
token = PasswordResetToken(
|
||||
user_id=user.id,
|
||||
token_hash=_hash_token(plain),
|
||||
created_at_utc=now,
|
||||
expires_at_utc=now + timedelta(hours=get_settings().PASSWORD_RESET_HOURS),
|
||||
used_at_utc=None,
|
||||
)
|
||||
db.add(token)
|
||||
db.commit()
|
||||
return plain
|
||||
|
||||
|
||||
def _validate_unused(record) -> bool:
|
||||
if not record or record.used_at_utc is not None:
|
||||
return False
|
||||
now = utcnow()
|
||||
exp = record.expires_at_utc
|
||||
if getattr(exp, "tzinfo", None) is None:
|
||||
exp = exp.replace(tzinfo=timezone.utc)
|
||||
return exp >= now
|
||||
|
||||
|
||||
def accept_invite(db: Session, token: str, password: str) -> User | None:
|
||||
err = validate_password_policy(password)
|
||||
if err:
|
||||
raise ValueError(err)
|
||||
record = db.execute(select(InviteToken).where(InviteToken.token_hash == _hash_token(token))).scalar_one_or_none()
|
||||
if not _validate_unused(record):
|
||||
return None
|
||||
user = db.execute(select(User).where(User.id == record.user_id)).scalar_one_or_none()
|
||||
if not user:
|
||||
return None
|
||||
user.password_hash = hash_password(password)
|
||||
user.must_change_password = False
|
||||
user.password_changed_at_utc = utcnow().replace(tzinfo=None)
|
||||
user.allow_login = True
|
||||
user.is_active = True
|
||||
record.used_at_utc = utcnow().replace(tzinfo=None)
|
||||
db.commit()
|
||||
return user
|
||||
|
||||
|
||||
def reset_password_with_token(db: Session, token: str, password: str) -> User | None:
|
||||
err = validate_password_policy(password)
|
||||
if err:
|
||||
raise ValueError(err)
|
||||
record = db.execute(select(PasswordResetToken).where(PasswordResetToken.token_hash == _hash_token(token))).scalar_one_or_none()
|
||||
if not _validate_unused(record):
|
||||
return None
|
||||
user = db.execute(select(User).where(User.id == record.user_id)).scalar_one_or_none()
|
||||
if not user:
|
||||
return None
|
||||
user.password_hash = hash_password(password)
|
||||
user.must_change_password = False
|
||||
user.password_changed_at_utc = utcnow().replace(tzinfo=None)
|
||||
record.used_at_utc = utcnow().replace(tzinfo=None)
|
||||
db.commit()
|
||||
return user
|
||||
|
||||
|
||||
def force_change_password(db: Session, user: User, new_password: str) -> None:
|
||||
err = validate_password_policy(new_password)
|
||||
if err:
|
||||
raise ValueError(err)
|
||||
user.password_hash = hash_password(new_password)
|
||||
user.must_change_password = False
|
||||
user.password_changed_at_utc = utcnow().replace(tzinfo=None)
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user