mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-01 15:46:07 -05:00
Merge pull request #3247 from alphagov/webauthn-login-endpoint
add endpoint for verifying webauthn login
This commit is contained in:
@@ -63,6 +63,7 @@ from app.user.users_schema import (
|
|||||||
post_send_user_sms_code_schema,
|
post_send_user_sms_code_schema,
|
||||||
post_set_permissions_schema,
|
post_set_permissions_schema,
|
||||||
post_verify_code_schema,
|
post_verify_code_schema,
|
||||||
|
post_verify_webauthn_schema,
|
||||||
)
|
)
|
||||||
from app.utils import url_with_token
|
from app.utils import url_with_token
|
||||||
|
|
||||||
@@ -226,6 +227,32 @@ def verify_user_code(user_id):
|
|||||||
return jsonify({}), 204
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
|
@user_blueprint.route('/<uuid:user_id>/verify/webauthn-login', methods=['POST'])
|
||||||
|
def verify_webauthn_login_for_user(user_id):
|
||||||
|
"""
|
||||||
|
webauthn logins are already verified on the admin app but we still need to
|
||||||
|
check the max login count and set up a session id etc here.
|
||||||
|
"""
|
||||||
|
data = request.get_json()
|
||||||
|
validate(data, post_verify_webauthn_schema)
|
||||||
|
|
||||||
|
user = get_user_by_id(user_id=user_id)
|
||||||
|
successful = data['successful']
|
||||||
|
|
||||||
|
if user.failed_login_count >= current_app.config.get('MAX_VERIFY_CODE_COUNT'):
|
||||||
|
raise InvalidRequest("Maximum login count exceeded", status_code=403)
|
||||||
|
|
||||||
|
if successful:
|
||||||
|
user.current_session_id = str(uuid.uuid4())
|
||||||
|
user.logged_in_at = datetime.utcnow()
|
||||||
|
user.failed_login_count = 0
|
||||||
|
save_model_user(user)
|
||||||
|
else:
|
||||||
|
increment_failed_login_count(user)
|
||||||
|
|
||||||
|
return jsonify({}), 204
|
||||||
|
|
||||||
|
|
||||||
@user_blueprint.route('/<uuid:user_id>/<code_type>-code', methods=['POST'])
|
@user_blueprint.route('/<uuid:user_id>/<code_type>-code', methods=['POST'])
|
||||||
def send_user_2fa_code(user_id, code_type):
|
def send_user_2fa_code(user_id, code_type):
|
||||||
user_to_send_to = get_user_by_id(user_id=user_id)
|
user_to_send_to = get_user_by_id(user_id=user_id)
|
||||||
|
|||||||
@@ -11,6 +11,18 @@ post_verify_code_schema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
post_verify_webauthn_schema = {
|
||||||
|
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||||
|
'description': 'POST schema for verifying a webauthn login attempt',
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'successful': {'type': 'boolean'}
|
||||||
|
},
|
||||||
|
'required': ['successful'],
|
||||||
|
'additionalProperties': False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
post_send_user_email_code_schema = {
|
post_send_user_email_code_schema = {
|
||||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||||
'description': (
|
'description': (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import pytest
|
import pytest
|
||||||
@@ -278,7 +278,7 @@ def test_post_user_attribute(client, sample_user, user_attribute, user_value):
|
|||||||
},
|
},
|
||||||
recipient='newuser@mail.com', reply_to_text='notify@gov.uk',
|
recipient='newuser@mail.com', reply_to_text='notify@gov.uk',
|
||||||
service=mock.ANY,
|
service=mock.ANY,
|
||||||
template_id=UUID('c73f1d71-4049-46d5-a647-d013bdeca3f0'), template_version=1
|
template_id=uuid.UUID('c73f1d71-4049-46d5-a647-d013bdeca3f0'), template_version=1
|
||||||
)),
|
)),
|
||||||
('mobile_number', '+4407700900460', dict(
|
('mobile_number', '+4407700900460', dict(
|
||||||
api_key_id=None, key_type='normal', notification_type='sms',
|
api_key_id=None, key_type='normal', notification_type='sms',
|
||||||
@@ -287,7 +287,7 @@ def test_post_user_attribute(client, sample_user, user_attribute, user_value):
|
|||||||
'email address': 'notify@digital.cabinet-office.gov.uk'
|
'email address': 'notify@digital.cabinet-office.gov.uk'
|
||||||
},
|
},
|
||||||
recipient='+4407700900460', reply_to_text='testing', service=mock.ANY,
|
recipient='+4407700900460', reply_to_text='testing', service=mock.ANY,
|
||||||
template_id=UUID('8a31520f-4751-4789-8ea1-fe54496725eb'), template_version=1
|
template_id=uuid.UUID('8a31520f-4751-4789-8ea1-fe54496725eb'), template_version=1
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
def test_post_user_attribute_with_updated_by(
|
def test_post_user_attribute_with_updated_by(
|
||||||
@@ -1149,3 +1149,68 @@ def test_get_sms_reply_to_for_notify_service(team_member_mobile_edit_template, n
|
|||||||
reply_to = get_sms_reply_to_for_notify_service(number, team_member_mobile_edit_template)
|
reply_to = get_sms_reply_to_for_notify_service(number, team_member_mobile_edit_template)
|
||||||
assert reply_to == current_app.config['NOTIFY_INTERNATIONAL_SMS_SENDER'] \
|
assert reply_to == current_app.config['NOTIFY_INTERNATIONAL_SMS_SENDER'] \
|
||||||
if expected_reply_to == 'notify_international_sender' else current_app.config['FROM_NUMBER']
|
if expected_reply_to == 'notify_international_sender' else current_app.config['FROM_NUMBER']
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time('2020-01-01 11:00')
|
||||||
|
def test_verify_webauthn_login_resets_login_if_succesful(admin_request, sample_user):
|
||||||
|
sample_user.failed_login_count = 1
|
||||||
|
|
||||||
|
assert sample_user.current_session_id is None
|
||||||
|
assert sample_user.logged_in_at is None
|
||||||
|
|
||||||
|
admin_request.post(
|
||||||
|
'user.verify_webauthn_login_for_user',
|
||||||
|
user_id=sample_user.id,
|
||||||
|
_data={'successful': True},
|
||||||
|
_expected_status=204
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sample_user.current_session_id is not None
|
||||||
|
assert sample_user.failed_login_count == 0
|
||||||
|
assert sample_user.logged_in_at == datetime(2020, 1, 1, 11, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_webauthn_login_returns_204_when_not_successful(admin_request, sample_user):
|
||||||
|
# when unsuccessful this endpoint is used to bump the failed count. the endpoint still worked
|
||||||
|
# properly so should return 204 (no content).
|
||||||
|
sample_user.failed_login_count = 1
|
||||||
|
|
||||||
|
assert sample_user.current_session_id is None
|
||||||
|
assert sample_user.logged_in_at is None
|
||||||
|
|
||||||
|
admin_request.post(
|
||||||
|
'user.verify_webauthn_login_for_user',
|
||||||
|
user_id=sample_user.id,
|
||||||
|
_data={'successful': False},
|
||||||
|
_expected_status=204
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sample_user.current_session_id is None
|
||||||
|
assert sample_user.failed_login_count == 2
|
||||||
|
assert sample_user.logged_in_at is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_webauthn_login_raises_403_if_max_login_count_exceeded(admin_request, sample_user):
|
||||||
|
# when unsuccessful this endpoint is used to bump the failed count. the endpoint still worked
|
||||||
|
# properly so should return 204 (no content).
|
||||||
|
sample_user.failed_login_count = 10
|
||||||
|
|
||||||
|
admin_request.post(
|
||||||
|
'user.verify_webauthn_login_for_user',
|
||||||
|
user_id=sample_user.id,
|
||||||
|
_data={'successful': True},
|
||||||
|
_expected_status=403
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sample_user.current_session_id is None
|
||||||
|
assert sample_user.failed_login_count == 10
|
||||||
|
assert sample_user.logged_in_at is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_webauthn_login_raises_400_if_schema_invalid(admin_request):
|
||||||
|
admin_request.post(
|
||||||
|
'user.verify_webauthn_login_for_user',
|
||||||
|
user_id=uuid.uuid4(),
|
||||||
|
_data={'successful': 'True'},
|
||||||
|
_expected_status=400
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user