mirror of
https://github.com/healthchecks/healthchecks.git
synced 2024-11-21 15:17:57 +00:00
e048ec4c48
In Python 3 these are equivalent, and shorter is better.
72 lines
2.5 KiB
Python
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
|