from __future__ import annotations import hashlib from typing import Any from datahub.crypto import SecretVault, mask_secret from datahub.db import HubDB from datahub.timeutil import isoformat def token_hash(token: str) -> str: return hashlib.sha256(token.encode("utf-8")).hexdigest() class AuthService: """Machine credentials only: the `/v1` API token and the provider secrets. Operator accounts live on the review site (HEL-560) — the console verifies them through `SiteAuth`, so nothing here authenticates a person. """ def __init__(self, db: HubDB, vault: SecretVault, api_token: str) -> None: self.db = db self.vault = vault self._bootstrap(api_token) def _bootstrap(self, api_token: str) -> None: if api_token: existing = self.db.fetchone("SELECT token_hash FROM api_tokens WHERE name = ?", ("review",)) hashed = token_hash(api_token) last4 = mask_secret(api_token) if existing is None: self.db.execute( "INSERT INTO api_tokens(token_hash, name, last4, created_at) VALUES (?,?,?,?)", (hashed, "review", last4, isoformat()), ) elif existing["token_hash"] != hashed: self.db.execute( "UPDATE api_tokens SET token_hash = ?, last4 = ? WHERE name = ?", (hashed, last4, "review"), ) def check_api_token(self, supplied: str) -> bool: if not supplied: return False row = self.db.fetchone( "SELECT token_hash FROM api_tokens WHERE token_hash = ? AND revoked_at IS NULL", (token_hash(supplied),), ) return row is not None def credential_status(self, name: str) -> dict[str, Any]: row = self.db.fetchone("SELECT last4, updated_at FROM credentials WHERE name = ?", (name,)) if not row: return {"configured": False, "last4": "", "updated_at": ""} return {"configured": True, "last4": row["last4"], "updated_at": row["updated_at"]} def store_credential(self, name: str, secret: str) -> None: payload = self.vault.encrypt_json({name: secret}) self.db.execute( """ INSERT INTO credentials(name, encrypted_payload, last4, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(name) DO UPDATE SET encrypted_payload=excluded.encrypted_payload, last4=excluded.last4, updated_at=excluded.updated_at """, (name, payload, mask_secret(secret), isoformat()), ) def load_credential(self, name: str) -> str: row = self.db.fetchone("SELECT encrypted_payload FROM credentials WHERE name = ?", (name,)) if not row: return "" data = self.vault.decrypt_json(str(row["encrypted_payload"])) return str(data.get(name) or "")