from datetime import datetime, timezone from fastapi import HTTPException, status from motor.motor_asyncio import AsyncIOMotorDatabase from pymongo.errors import DuplicateKeyError from app.core.security import hash_auth_key, normalize_identifier, verify_auth_key from app.schemas.auth import AuthLoginRequest, AuthSignupRequest ACCOUNTS_COLLECTION = "accounts" async def ensure_auth_indexes(database: AsyncIOMotorDatabase) -> None: await database[ACCOUNTS_COLLECTION].create_index( [("identifierType", 1), ("identifier", 1)], unique=True, name="uq_accounts_identifier", ) def _account_filter(identifier_type: str, identifier: str) -> dict[str, str]: return { "identifierType": identifier_type, "identifier": normalize_identifier(identifier_type, identifier), } async def create_account(database: AsyncIOMotorDatabase, payload: AuthSignupRequest) -> str: now = datetime.now(timezone.utc) account_document = { "identifierType": payload.identifierType.value, "identifier": normalize_identifier(payload.identifierType.value, payload.identifier), "hashedAuthKey": hash_auth_key(payload.authKeyMaterial), "saltAuth": payload.saltAuth, "saltEnc": payload.saltEnc, "createdAt": now, "updatedAt": now, } try: result = await database[ACCOUNTS_COLLECTION].insert_one(account_document) except DuplicateKeyError as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="An account with that identifier already exists.", ) from exc return str(result.inserted_id) async def get_account_by_identifier(database: AsyncIOMotorDatabase, identifier_type: str, identifier: str) -> dict[str, str]: account = await database[ACCOUNTS_COLLECTION].find_one(_account_filter(identifier_type, identifier)) if account is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Account not found.", ) return account async def verify_account_login(database: AsyncIOMotorDatabase, payload: AuthLoginRequest) -> str: account = await get_account_by_identifier(database, payload.identifierType.value, payload.identifier) if not verify_auth_key(payload.authKeyMaterial, account["hashedAuthKey"]): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials.", ) return str(account["_id"])