Merge pull request #2934 from coreos-inc/joseph.schorr/QS-78/email-recovery
Security fixes for password recovery
This commit is contained in:
commit
b9ad8bbb5d
4 changed files with 55 additions and 7 deletions
|
@ -806,6 +806,10 @@ class Recovery(ApiResource):
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'description': 'The user\'s email address',
|
'description': 'The user\'s email address',
|
||||||
},
|
},
|
||||||
|
'recaptcha_response': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'The (may be disabled) recaptcha response code for verification',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -826,10 +830,26 @@ class Recovery(ApiResource):
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
email = request.get_json()['email']
|
recovery_data = request.get_json()
|
||||||
|
|
||||||
|
# If recaptcha is enabled, then verify the user is a human.
|
||||||
|
if features.RECAPTCHA:
|
||||||
|
recaptcha_response = recovery_data.get('recaptcha_response', '')
|
||||||
|
result = recaptcha2.verify(app.config['RECAPTCHA_SECRET_KEY'],
|
||||||
|
recaptcha_response,
|
||||||
|
request.remote_addr)
|
||||||
|
|
||||||
|
if not result['success']:
|
||||||
|
return {
|
||||||
|
'message': 'Are you a bot? If not, please revalidate the captcha.'
|
||||||
|
}, 400
|
||||||
|
|
||||||
|
email = recovery_data['email']
|
||||||
user = model.user.find_user_by_email(email)
|
user = model.user.find_user_by_email(email)
|
||||||
if not user:
|
if not user:
|
||||||
raise model.InvalidEmailAddressException('Email address was not found.')
|
return {
|
||||||
|
'status': 'sent',
|
||||||
|
}
|
||||||
|
|
||||||
if user.organization:
|
if user.organization:
|
||||||
send_org_recovery_email(user, model.organization.get_admin_users(user))
|
send_org_recovery_email(user, model.organization.get_admin_users(user))
|
||||||
|
|
|
@ -5,4 +5,24 @@
|
||||||
|
|
||||||
.recovery-form-element input {
|
.recovery-form-element input {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recovery-form-element .captcha {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recovery-form-element .captcha div {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recovery-form-element .captcha {
|
||||||
|
height: 0px;
|
||||||
|
transition: height ease-in-out 250ms;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recovery-form-element .captcha.expanded {
|
||||||
|
height: 94px;
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!sendingRecovery">
|
<div ng-show="!sendingRecovery">
|
||||||
<div class="co-alert co-alert-success" ng-show="sent.status == 'sent'">
|
<div class="co-alert co-alert-success" ng-show="sent.status == 'sent'">
|
||||||
Account recovery email was sent to {{ recovery.email }}.
|
Instructions on how to reset your password have been sent to {{ recovery.email }}. If you do not receive the email, please try again shortly.
|
||||||
</div>
|
</div>
|
||||||
<div class="co-alert co-alert-danger" ng-show="invalidRecovery">{{ errorMessage }}</div>
|
<div class="co-alert co-alert-danger" ng-show="invalidRecovery">{{ errorMessage }}</div>
|
||||||
<div class="co-alert co-alert-info" ng-show="sent.status == 'org'">
|
<div class="co-alert co-alert-info" ng-show="sent.status == 'org'">
|
||||||
|
@ -18,6 +18,14 @@
|
||||||
|
|
||||||
<form class="form-signin" ng-submit="sendRecovery()" ng-show="!sent">
|
<form class="form-signin" ng-submit="sendRecovery()" ng-show="!sent">
|
||||||
<input type="text" class="form-control" placeholder="Email" ng-model="recovery.email">
|
<input type="text" class="form-control" placeholder="Email" ng-model="recovery.email">
|
||||||
|
|
||||||
|
<div quay-require="['RECAPTCHA']">
|
||||||
|
<div class="captcha"
|
||||||
|
ng-class="{'expanded': recovery.email}">
|
||||||
|
<div vc-recaptcha ng-model="recovery.recaptcha_response" key="Config.RECAPTCHA_SITE_KEY"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary btn-block" type="submit">Send Recovery Email</button>
|
<button class="btn btn-primary btn-block" type="submit">Send Recovery Email</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -457,16 +457,16 @@ class TestRecovery(ApiTestCase):
|
||||||
self._set_url(Recovery)
|
self._set_url(Recovery)
|
||||||
|
|
||||||
def test_post_anonymous(self):
|
def test_post_anonymous(self):
|
||||||
self._run_test('POST', 400, None, {u'email': '826S'})
|
self._run_test('POST', 200, None, {u'email': '826S'})
|
||||||
|
|
||||||
def test_post_freshuser(self):
|
def test_post_freshuser(self):
|
||||||
self._run_test('POST', 400, 'freshuser', {u'email': '826S'})
|
self._run_test('POST', 200, 'freshuser', {u'email': '826S'})
|
||||||
|
|
||||||
def test_post_reader(self):
|
def test_post_reader(self):
|
||||||
self._run_test('POST', 400, 'reader', {u'email': '826S'})
|
self._run_test('POST', 200, 'reader', {u'email': '826S'})
|
||||||
|
|
||||||
def test_post_devtable(self):
|
def test_post_devtable(self):
|
||||||
self._run_test('POST', 400, 'devtable', {u'email': '826S'})
|
self._run_test('POST', 200, 'devtable', {u'email': '826S'})
|
||||||
|
|
||||||
|
|
||||||
class TestSignout(ApiTestCase):
|
class TestSignout(ApiTestCase):
|
||||||
|
|
Reference in a new issue