102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
"""
|
|
Script to create a shop account with an operator-supplied strong password.
|
|
|
|
Usage:
|
|
python -m app.db.create_shop_account
|
|
|
|
Provide SHOP_ACCOUNT_PASSWORD for non-interactive execution,
|
|
or enter the password securely via getpass().
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from getpass import getpass
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.core.security import get_password_hash
|
|
from app.db.session import SessionLocal, engine
|
|
from app.models import Base, RoleEnum, User
|
|
|
|
SHOP_USERNAME = "shop"
|
|
MIN_PASSWORD_LENGTH = 12
|
|
|
|
|
|
def _validate_password_strength(password: str) -> None:
|
|
if len(password) < MIN_PASSWORD_LENGTH or len(password) > 72:
|
|
raise ValueError("Password must be between 12 and 72 characters long.")
|
|
has_upper = any(char.isupper() for char in password)
|
|
has_lower = any(char.islower() for char in password)
|
|
has_digit = any(char.isdigit() for char in password)
|
|
has_special = any(not char.isalnum() for char in password)
|
|
if not all((has_upper, has_lower, has_digit, has_special)):
|
|
raise ValueError(
|
|
"Password must include upper, lower, digit, and special characters."
|
|
)
|
|
|
|
|
|
def _get_shop_password() -> str:
|
|
env_password = os.getenv("SHOP_ACCOUNT_PASSWORD", "").strip()
|
|
if env_password:
|
|
_validate_password_strength(env_password)
|
|
return env_password
|
|
|
|
if not sys.stdin.isatty():
|
|
raise RuntimeError(
|
|
"Interactive password input is unavailable. Set SHOP_ACCOUNT_PASSWORD to create the account safely."
|
|
)
|
|
|
|
while True:
|
|
password = getpass("Enter a strong password for the shop account: ")
|
|
confirm_password = getpass("Confirm the password: ")
|
|
if password != confirm_password:
|
|
print("Passwords do not match. Please try again.", file=sys.stderr)
|
|
continue
|
|
_validate_password_strength(password)
|
|
return password
|
|
|
|
|
|
def create_shop_account() -> None:
|
|
"""Create a shop account with a strong password if it doesn't exist."""
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
with SessionLocal() as db:
|
|
existing = db.scalar(
|
|
select(User).where(User.username == SHOP_USERNAME)
|
|
)
|
|
if existing is not None:
|
|
print(f"Shop account '{SHOP_USERNAME}' already exists (id={existing.id}).")
|
|
print("No new account was created.")
|
|
return
|
|
|
|
password = _get_shop_password()
|
|
password_hash = get_password_hash(password)
|
|
|
|
shop = User(
|
|
username=SHOP_USERNAME,
|
|
password_hash=password_hash,
|
|
role=RoleEnum.shop,
|
|
is_active=True,
|
|
)
|
|
db.add(shop)
|
|
db.commit()
|
|
db.refresh(shop)
|
|
|
|
print("=" * 60)
|
|
print("SHOP ACCOUNT CREATED SUCCESSFULLY")
|
|
print("=" * 60)
|
|
print(f"Username: {SHOP_USERNAME}")
|
|
print("=" * 60)
|
|
print("Password was accepted and hashed without being printed to stdout.")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
create_shop_account()
|
|
except Exception as exc:
|
|
print(f"Error: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|