datasette-auth-passwords/datasette_auth_passwords/utils.py
import base64import hashlibimport secretsALGORITHM = "pbkdf2_sha256"def hash_password(password, salt=None, iterations=480000):if salt is None:salt = secrets.token_hex(16)assert salt and isinstance(salt, str) and "$" not in saltassert isinstance(password, str)pw_hash = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt.encode("utf-8"), iterations)b64_hash = base64.b64encode(pw_hash).decode("ascii").strip()return "{}${}${}${}".format(ALGORITHM, iterations, salt, b64_hash)def verify_password(password, password_hash):if (password_hash or "").count("$") != 3:return Falsepassword_hash = password_hash.strip()algorithm, iterations, salt, b64_hash = password_hash.split("$", 3)iterations = int(iterations)assert algorithm == ALGORITHMcompare_hash = hash_password(password, salt, iterations)return secrets.compare_digest(password_hash, compare_hash)def scope_has_valid_authorization(scope, datasette):config = datasette.plugin_config("datasette-auth-passwords") or {}accounts = {key.split("_password_hash")[0]: valuefor key, value in config.items()if key.endswith("_password_hash")}actors = config.get("actors") or {}headers = dict(scope.get("headers") or {})authorization = headers.get(b"authorization") or b""if not authorization.startswith(b"Basic "):return Nonecredentials = authorization.split(b"Basic ")[1]decoded = base64.b64decode(credentials).decode("ascii")username, _, password = decoded.partition(":")password_hash = accounts.get(username)if password_hash and verify_password(password, password_hash):return actors.get(username) or {"id": username}else:return None