68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
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"])
|