Files
notifications-admin/app/main/views/webauthn_credentials.py

114 lines
3.6 KiB
Python

from fido2 import cbor
from fido2.client import ClientData
from fido2.ctap2 import AuthenticatorData
from flask import abort, current_app, request, session
from flask_login import current_user
from app.main import main
from app.models.user import User
from app.models.webauthn_credential import RegistrationError, WebAuthnCredential
from app.notify_client.user_api_client import user_api_client
from app.utils import redirect_to_sign_in, user_is_platform_admin
@main.route('/webauthn/register')
@user_is_platform_admin
def webauthn_begin_register():
server = current_app.webauthn_server
registration_data, state = server.register_begin(
{
"id": bytes(current_user.id, 'utf-8'),
"name": current_user.email_address,
"displayName": current_user.name,
},
credentials=[
credential.to_credential_data()
for credential in current_user.webauthn_credentials
],
user_verification="discouraged", # don't ask for PIN
authenticator_attachment="cross-platform",
)
session["webauthn_registration_state"] = state
return cbor.encode(registration_data)
@main.route('/webauthn/register', methods=['POST'])
@user_is_platform_admin
def webauthn_complete_register():
if 'webauthn_registration_state' not in session:
return cbor.encode("No registration in progress"), 400
try:
credential = WebAuthnCredential.from_registration(
session.pop("webauthn_registration_state"),
cbor.decode(request.get_data()),
)
except RegistrationError as e:
return cbor.encode(str(e)), 400
user_api_client.create_webauthn_credential_for_user(
current_user.id, credential
)
return cbor.encode('')
@main.route('/webauthn/authenticate', methods=['GET'])
@redirect_to_sign_in
def webauthn_begin_authentication():
# get user from session
user_to_login = User.from_id(session['user_details']['id'])
if not user_to_login.webauthn_auth:
abort(403)
if not user_to_login.platform_admin:
abort(403)
authentication_data, state = current_app.webauthn_server.authenticate_begin(
credentials=[
credential.to_credential_data()
for credential in user_to_login.webauthn_credentials
],
user_verification=None, # required, preferred, discouraged. sets whether to ask for PIN
)
session["webauthn_authentication_state"] = state
return cbor.encode(authentication_data)
@main.route('/webauthn/authenticate', methods=['POST'])
@redirect_to_sign_in
def webauthn_complete_authentication():
user_id = session['user_details']['id']
user_to_login = User.from_id(user_id)
if not user_to_login.webauthn_auth:
abort(403)
if not user_to_login.platform_admin:
abort(403)
state = session.pop("webauthn_authentication_state")
request_data = cbor.decode(request.get_data())
try:
current_app.webauthn_server.authenticate_complete(
state=state,
credentials=[
credential.to_credential_data()
for credential in user_to_login.webauthn_credentials
],
credential_id=request_data['credentialId'],
client_data=ClientData(request_data['clientDataJSON']),
auth_data=AuthenticatorData(request_data['authenticatorData']),
signature=request_data['signature']
)
except ValueError as exc:
current_app.logger.info(f'User {user_id} could not sign in using their webauthn token - {exc}')
abort(403)
from app.main.views.two_factor import log_in_user
return log_in_user(user_id)