healthchecks_healthchecks/hc/lib/webauthn.py
Pēteris Caune e048ec4c48
Fix "class Foo(object):" -> "class Foo:"
In Python 3 these are equivalent, and shorter is better.
2024-10-29 17:57:50 +02:00

72 lines
2.5 KiB
Python

from __future__ import annotations
import json
from collections.abc import Iterable
from secrets import token_bytes
from typing import Any
import fido2.features
from fido2.server import Fido2Server
from fido2.webauthn import (
AttestedCredentialData,
PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
UserVerificationRequirement,
)
fido2.features.webauthn_json_mapping.enabled = True
class CreateHelper:
def __init__(self, rp_id: str, credentials: Iterable[bytes]):
rp = PublicKeyCredentialRpEntity(id=rp_id, name="healthchecks")
self.server = Fido2Server(rp)
self.credentials = [AttestedCredentialData(blob) for blob in credentials]
def prepare(self, email: str) -> tuple[dict[str, Any], Any]:
# User handle (id) is used in a username-less authentication, to map a
# credential received from browser with an user account in the database.
# Since we only use security keys as a second factor,
# the user handle is not of much use to us.
#
# The user handle:
# - must not be blank,
# - must not be a constant value,
# - must not contain personally identifiable information.
# So we use random bytes, and don't store them on our end:
user = PublicKeyCredentialUserEntity(
id=token_bytes(16),
name=email,
display_name=email,
)
options, state = self.server.register_begin(
user,
self.credentials,
user_verification=UserVerificationRequirement.DISCOURAGED,
)
return dict(options), state
def verify(self, state: Any, response_json: str) -> bytes | None:
doc = json.loads(response_json)
auth_data = self.server.register_complete(state, doc)
return auth_data.credential_data
class GetHelper:
def __init__(self, rp_id: str, credentials: Iterable[bytes]):
rp = PublicKeyCredentialRpEntity(id=rp_id, name="healthchecks")
self.server = Fido2Server(rp)
self.credentials = [AttestedCredentialData(blob) for blob in credentials]
def prepare(self) -> tuple[dict[str, Any], Any]:
options, state = self.server.authenticate_begin(self.credentials)
return dict(options), state
def verify(self, state: Any, response_json: str) -> bool:
try:
doc = json.loads(response_json)
self.server.authenticate_complete(state, self.credentials, doc)
return True
except ValueError:
return False