Add ability to view and change an account’s email address
This commit is contained in:
parent
d5bbea9fb2
commit
a363ada41c
8 changed files with 123 additions and 13 deletions
|
@ -154,6 +154,7 @@ class EmailConfirmation(BaseModel):
|
|||
code = CharField(default=random_string_generator(), unique=True, index=True)
|
||||
user = ForeignKeyField(User)
|
||||
pw_reset = BooleanField(default=False)
|
||||
new_email = CharField(null=True, unique=True)
|
||||
email_confirm = BooleanField(default=False)
|
||||
created = DateTimeField(default=datetime.now)
|
||||
|
||||
|
|
|
@ -337,8 +337,12 @@ def list_federated_logins(user):
|
|||
FederatedLogin.user == user)
|
||||
|
||||
|
||||
def create_confirm_email_code(user):
|
||||
code = EmailConfirmation.create(user=user, email_confirm=True)
|
||||
def create_confirm_email_code(user, new_email=None):
|
||||
if new_email:
|
||||
if not validate_email(new_email):
|
||||
raise InvalidEmailAddressException('Invalid email address: %s' % new_email)
|
||||
|
||||
code = EmailConfirmation.create(user=user, email_confirm=True, new_email=new_email)
|
||||
return code
|
||||
|
||||
|
||||
|
@ -347,15 +351,20 @@ def confirm_user_email(code):
|
|||
code = EmailConfirmation.get(EmailConfirmation.code == code,
|
||||
EmailConfirmation.email_confirm == True)
|
||||
except EmailConfirmation.DoesNotExist:
|
||||
raise DataModelException('Invalid email confirmation code.')
|
||||
raise DataModelException('Invalid email confirmation code.')
|
||||
|
||||
user = code.user
|
||||
user.verified = True
|
||||
|
||||
new_email = code.new_email
|
||||
if new_email:
|
||||
user.email = new_email
|
||||
|
||||
user.save()
|
||||
|
||||
code.delete_instance()
|
||||
|
||||
return user
|
||||
return {'user': user, 'new_email': new_email}
|
||||
|
||||
|
||||
def create_reset_password_email_code(email):
|
||||
|
@ -384,6 +393,13 @@ def validate_reset_code(code):
|
|||
return user
|
||||
|
||||
|
||||
def find_user_by_email(email):
|
||||
try:
|
||||
return User.get(User.email == email)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def get_user(username):
|
||||
try:
|
||||
return User.get(User.username == username, User.organization == False)
|
||||
|
|
|
@ -14,7 +14,7 @@ from data import model
|
|||
from data.queue import dockerfile_build_queue
|
||||
from data.plans import PLANS, get_plan
|
||||
from app import app
|
||||
from util.email import send_confirmation_email, send_recovery_email
|
||||
from util.email import send_confirmation_email, send_recovery_email, send_change_email
|
||||
from util.names import parse_repository_name, format_robot_username
|
||||
from util.gravatar import compute_hash
|
||||
|
||||
|
@ -264,6 +264,20 @@ def change_user_details():
|
|||
logger.debug('Changing invoice_email for user: %s', user.username)
|
||||
model.change_invoice_email(user, user_data['invoice_email'])
|
||||
|
||||
if 'email' in user_data and user_data['email'] != user.email:
|
||||
new_email = user_data['email']
|
||||
if model.find_user_by_email(new_email):
|
||||
# Email already used.
|
||||
error_resp = jsonify({
|
||||
'message': 'E-mail address already used'
|
||||
})
|
||||
error_resp.status_code = 400
|
||||
return error_resp
|
||||
|
||||
logger.debug('Sending email to change email address for user: %s', user.username)
|
||||
code = model.create_confirm_email_code(user, new_email=new_email)
|
||||
send_change_email(user.username, user_data['email'], code.code)
|
||||
|
||||
except model.InvalidPasswordException, ex:
|
||||
error_resp = jsonify({
|
||||
'message': ex.message,
|
||||
|
|
|
@ -257,13 +257,13 @@ def confirm_email():
|
|||
code = request.values['code']
|
||||
|
||||
try:
|
||||
user = model.confirm_user_email(code)
|
||||
result = model.confirm_user_email(code)
|
||||
except model.DataModelException as ex:
|
||||
return redirect(url_for('signin'))
|
||||
|
||||
common_login(user)
|
||||
common_login(result['user'])
|
||||
|
||||
return redirect(url_for('index'))
|
||||
return redirect(url_for('user', tab='email') if result['new_email'] else url_for('index'))
|
||||
|
||||
|
||||
@app.route('/recovery', methods=['GET'])
|
||||
|
|
|
@ -1850,6 +1850,12 @@ p.editable:hover i {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.user-admin .panel-setting-content {
|
||||
margin-left: 16px;
|
||||
margin-top: 16px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.user-admin .plan-description {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
|
@ -1860,7 +1866,7 @@ p.editable:hover i {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-admin .form-change-pw input {
|
||||
.user-admin .form-change input {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
|
|
@ -906,12 +906,13 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
$scope.loading = true;
|
||||
$scope.updatingUser = false;
|
||||
$scope.changePasswordSuccess = false;
|
||||
$scope.changeEmailSent = false;
|
||||
$scope.convertStep = 0;
|
||||
$scope.org = {};
|
||||
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
||||
$scope.githubClientId = KeyService.githubClientId;
|
||||
|
||||
$('.form-change-pw').popover();
|
||||
$('.form-change').popover();
|
||||
|
||||
$scope.logsShown = 0;
|
||||
$scope.invoicesShown = 0;
|
||||
|
@ -970,8 +971,31 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
});
|
||||
};
|
||||
|
||||
$scope.changeEmail = function() {
|
||||
$('#changeEmailForm').popover('hide');
|
||||
$scope.updatingUser = true;
|
||||
$scope.changeEmailSent = false;
|
||||
|
||||
ApiService.changeUserDetails($scope.cuser).then(function() {
|
||||
$scope.updatingUser = false;
|
||||
$scope.changeEmailSent = true;
|
||||
$scope.sentEmail = $scope.cuser.email;
|
||||
|
||||
// Reset the form.
|
||||
$scope.cuser.repeatEmail = '';
|
||||
$scope.changeEmailForm.$setPristine();
|
||||
}, function(result) {
|
||||
$scope.updatingUser = false;
|
||||
|
||||
$scope.changeEmailError = result.data.message;
|
||||
$timeout(function() {
|
||||
$('#changeEmailForm').popover('show');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changePassword = function() {
|
||||
$('.form-change-pw').popover('hide');
|
||||
$('#changePasswordForm').popover('hide');
|
||||
$scope.updatingUser = true;
|
||||
$scope.changePasswordSuccess = false;
|
||||
|
||||
|
@ -991,7 +1015,7 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
|
||||
$scope.changePasswordError = result.data.message;
|
||||
$timeout(function() {
|
||||
$('.form-change-pw').popover('show');
|
||||
$('#changePasswordForm').popover('show');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billingoptions">Billing Options</a></li>
|
||||
<li ng-show="hasPaidBusinessPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing" ng-click="loadInvoices()">Billing History</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github">GitHub Login</a></li>
|
||||
<li ng-show="hasPaidBusinessPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
|
||||
|
@ -50,6 +51,36 @@
|
|||
<div class="plan-manager" user="user.username" ready-for-plan="readyForPlan()" plan-changed="planChanged(plan)"></div>
|
||||
</div>
|
||||
|
||||
<!-- E-mail address tab -->
|
||||
<div id="email" class="tab-pane">
|
||||
<div class="row">
|
||||
<div class="alert alert-success" ng-show="changeEmailSent">An e-mail has been sent to {{ sentEmail }} to verify the change.</div>
|
||||
|
||||
<div class="loading" ng-show="updatingUser">
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel" ng-show="!updatingUser">
|
||||
<div class="panel-title">Account e-mail address</div>
|
||||
<div class="panel-setting-content">
|
||||
{{ user.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" ng-show="!updatingUser" >
|
||||
<div class="panel-title">Change e-mail address</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<form class="form-change col-md-6" id="changeEmailForm" name="changeEmailForm" ng-submit="changeEmail()" data-trigger="manual"
|
||||
data-content="{{ changeEmailError }}" data-placement="right" ng-show="!awaitingConfirmation && !registering">
|
||||
<input type="email" class="form-control" placeholder="Your new e-mail address" ng-model="cuser.email" required>
|
||||
<button class="btn btn-primary" ng-disabled="changeEmailForm.$invalid || cuser.email == user.email" type="submit">Change E-mail Address</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change password tab -->
|
||||
<div id="password" class="tab-pane">
|
||||
<div class="loading" ng-show="updatingUser">
|
||||
|
@ -62,7 +93,7 @@
|
|||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||
|
||||
<div ng-show="!updatingUser" class="panel-body">
|
||||
<form class="form-change-pw col-md-6" name="changePasswordForm" ng-submit="changePassword()" data-trigger="manual"
|
||||
<form class="form-change col-md-6" id="changePasswordForm" name="changePasswordForm" ng-submit="changePassword()" data-trigger="manual"
|
||||
data-content="{{ changePasswordError }}" data-placement="right" ng-show="!awaitingConfirmation && !registering">
|
||||
<input type="password" class="form-control" placeholder="Your new password" ng-model="cuser.password" required>
|
||||
<input type="password" class="form-control" placeholder="Verify your new password" ng-model="cuser.repeatPassword"
|
||||
|
@ -75,6 +106,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Github tab -->
|
||||
<div id="github" class="tab-pane">
|
||||
<div class="loading" ng-show="!cuser">
|
||||
<div class="quay-spinner 3x"></div>
|
||||
|
|
|
@ -12,6 +12,15 @@ To confirm this email address, please click the following link:<br>
|
|||
"""
|
||||
|
||||
|
||||
CHANGE_MESSAGE = """
|
||||
This email address was recently asked to become the new e-mail address for username '%s'
|
||||
at <a href="https://quay.io">Quay.io</a>.<br>
|
||||
<br>
|
||||
To confirm this email address, please click the following link:<br>
|
||||
<a href="https://quay.io/confirm?code=%s">https://quay.io/confirm?code=%s</a>
|
||||
"""
|
||||
|
||||
|
||||
RECOVERY_MESSAGE = """
|
||||
A user at <a href="https://quay.io">Quay.io</a> has attempted to recover their account
|
||||
using this email.<br>
|
||||
|
@ -25,6 +34,14 @@ not given access. Please disregard this email.<br>
|
|||
"""
|
||||
|
||||
|
||||
def send_change_email(username, email, token):
|
||||
msg = Message('Quay.io email change. Please confirm your email.',
|
||||
sender='support@quay.io', # Why do I need this?
|
||||
recipients=[email])
|
||||
msg.html = CHANGE_MESSAGE % (username, token, token)
|
||||
mail.send(msg)
|
||||
|
||||
|
||||
def send_confirmation_email(username, email, token):
|
||||
msg = Message('Welcome to Quay.io! Please confirm your email.',
|
||||
sender='support@quay.io', # Why do I need this?
|
||||
|
|
Reference in a new issue