def account_edit(newprofile=False): form = ProfileForm(obj=current_auth.user) form.edit_user = current_auth.user form.fullname.description = current_app.config.get('FULLNAME_REASON') form.email.description = current_app.config.get('EMAIL_REASON') form.username.description = current_app.config.get('USERNAME_REASON') form.timezone.description = current_app.config.get('TIMEZONE_REASON') if current_auth.user.email or newprofile is False: del form.email if form.validate_on_submit(): # Can't auto-populate here because user.email is read-only current_auth.user.fullname = form.fullname.data current_auth.user.username = form.username.data current_auth.user.timezone = form.timezone.data if newprofile and not current_auth.user.email: useremail = UserEmailClaim.get(user=current_auth.user, email=form.email.data) if useremail is None: useremail = UserEmailClaim(user=current_auth.user, email=form.email.data) db.session.add(useremail) send_email_verify_link(useremail) db.session.commit() user_data_changed.send(current_auth.user, changes=['profile', 'email-claim']) flash(_( "Your profile has been updated. We sent you an email to confirm your address" ), category='success') else: db.session.commit() user_data_changed.send(current_auth.user, changes=['profile']) flash(_("Your profile has been updated"), category='success') if newprofile: return render_redirect(get_next_url(), code=303) else: return render_redirect(url_for('account'), code=303) if newprofile: return render_form( form, title=_("Update profile"), formid='account_new', submit=_("Continue"), message=Markup( _(u"Hello, <strong>{fullname}</strong>. Please spare a minute to fill out your profile" ).format(fullname=escape(current_auth.user.fullname))), ajax=True) else: return render_form(form, title=_("Edit profile"), formid='account_edit', submit=_("Save changes"), ajax=True)
def register(): form = RegisterForm() # Make Recaptcha optional if not (current_app.config.get('RECAPTCHA_PUBLIC_KEY') and current_app.config.get('RECAPTCHA_PRIVATE_KEY')): del form.recaptcha form.fullname.description = current_app.config.get('FULLNAME_REASON') form.email.description = current_app.config.get('EMAIL_REASON') form.username.description = current_app.config.get('USERNAME_REASON') if form.validate_on_submit(): user = register_internal(None, form.fullname.data, form.password.data) user.username = form.username.data or None useremail = UserEmailClaim(user=user, email=form.email.data) db.session.add(useremail) send_email_verify_link(useremail) login_internal(user) db.session.commit() flash("You are now one of us. Welcome aboard!", category='success') if 'next' in request.args: return redirect(request.args['next'], code=303) else: return redirect(url_for('index'), code=303) return render_form(form=form, title='Create an account', formid='register', submit='Register')
def verify_email(md5sum): """ If the user has a pending email verification but has lost the email, allow them to send themselves another verification email. This endpoint is only linked to from the account page under the list of email addresses pending verification. """ useremail = UserEmail.get(md5sum=md5sum) if useremail and useremail.user == current_auth.user: # If an email address is already verified (this should not happen unless the # user followed a stale link), tell them it's done -- but only if the email # address belongs to this user, to prevent this endpoint from being used as a # probe for email addresses in the database. flash(_("This email address is already verified"), 'danger') return render_redirect(url_for('.account'), code=303) # Get the existing email claim that we're resending a verification link for emailclaim = UserEmailClaim.get_for(user=current_auth.user, md5sum=md5sum) if not emailclaim: abort(404) verify_form = VerifyEmailForm() if verify_form.validate_on_submit(): send_email_verify_link(emailclaim) flash(_("The verification email has been sent to this address"), 'success') return render_redirect(url_for('.account'), code=303) return render_form( form=verify_form, title=_("Resend the verification email?"), message=_("We will resend the verification email to '{email}'".format( email=emailclaim.email)), formid="email_verify", submit=_("Send"), cancel_url=url_for('.account'), )
def register(): if current_auth.is_authenticated: return redirect(url_for('index')) form = RegisterForm() # Make Recaptcha optional if not (current_app.config.get('RECAPTCHA_PUBLIC_KEY') and current_app.config.get('RECAPTCHA_PRIVATE_KEY')): del form.recaptcha form.fullname.description = current_app.config.get('FULLNAME_REASON') form.email.description = current_app.config.get('EMAIL_REASON') form.username.description = current_app.config.get('USERNAME_REASON') if form.validate_on_submit(): user = register_internal(form.username.data, form.fullname.data, form.password.data) useremail = UserEmailClaim(user=user, email=form.email.data) db.session.add(useremail) send_email_verify_link(useremail) login_internal(user) db.session.commit() flash(_("You are now one of us. Welcome aboard!"), category='success') return redirect(get_next_url(session=True), code=303) return render_form( form=form, title=_("Create an account"), formid='register', submit=_("Register"), message=current_app.config.get('CREATE_ACCOUNT_MESSAGE'))
def add_email(): form = NewEmailAddressForm() if form.validate_on_submit(): useremail = UserEmailClaim.get(user=g.user, email=form.email.data) if useremail is None: useremail = UserEmailClaim(user=g.user, email=form.email.data) db.session.add(useremail) db.session.commit() send_email_verify_link(useremail) flash("We sent you an email to confirm your address.", 'success') user_data_changed.send(g.user, changes=['email-claim']) return render_redirect(url_for('.profile'), code=303) return render_form(form=form, title="Add an email address", formid="email_add", submit="Add email", ajax=True)
def validate_email(self, field): field.data = field.data.lower() # Convert to lowercase existing = UserEmail.get(email=field.data) if existing is not None: if existing.user == g.user: raise forms.ValidationError(_("You have already registered this email address")) else: raise forms.ValidationError(_("This email address has already been claimed")) existing = UserEmailClaim.get(email=field.data, user=g.user) if existing is not None: raise forms.ValidationError(_("This email address is pending verification"))
def add_email(): form = NewEmailAddressForm() if form.validate_on_submit(): useremail = UserEmailClaim.get(user=current_auth.user, email=form.email.data) if useremail is None: useremail = UserEmailClaim(user=current_auth.user, email=form.email.data, type=form.type.data) db.session.add(useremail) db.session.commit() send_email_verify_link(useremail) flash(_("We sent you an email to confirm your address"), 'success') user_data_changed.send(current_auth.user, changes=['email-claim']) return render_redirect(url_for('.account'), code=303) return render_form(form=form, title=_("Add an email address"), formid='email_add', submit=_("Add email"), ajax=True)
def profile_edit(newprofile=False): form = ProfileForm(obj=g.user) form.fullname.description = current_app.config.get('FULLNAME_REASON') form.email.description = current_app.config.get('EMAIL_REASON') form.username.description = current_app.config.get('USERNAME_REASON') form.description.description = current_app.config.get('BIO_REASON') form.timezone.description = current_app.config.get('TIMEZONE_REASON') if g.user.email or newprofile is False: del form.email if form.validate_on_submit(): # Can't auto-populate here because user.email is read-only g.user.fullname = form.fullname.data g.user.username = form.username.data g.user.description = form.description.data g.user.timezone = form.timezone.data if newprofile and not g.user.email: useremail = UserEmailClaim(user=g.user, email=form.email.data) db.session.add(useremail) send_email_verify_link(useremail) db.session.commit() user_data_changed.send(g.user, changes=['profile', 'email-claim']) flash( "Your profile has been updated. We sent you an email to confirm your address", category='success') else: db.session.commit() user_data_changed.send(g.user, changes=['profile']) flash("Your profile has been updated.", category='success') if newprofile: return render_redirect(get_next_url(), code=303) else: return render_redirect(url_for('profile'), code=303) if newprofile: return render_form( form, title="Update profile", formid="profile_new", submit="Continue", message= u"Hello, %s. Please spare a minute to fill out your profile." % g.user.fullname, ajax=True) else: return render_form(form, title="Edit profile", formid="profile_edit", submit="Save changes", ajax=True)
def validate_email(self, field): field.data = field.data.lower() # Convert to lowercase existing = UserEmail.get(email=field.data) if existing is not None: if existing.user == g.user: raise wtforms.ValidationError( "You have already registered this email address.") else: raise wtforms.ValidationError( "This email address has already been claimed.") existing = UserEmailClaim.get(email=field.data, user=g.user) if existing is not None: raise wtforms.ValidationError( "This email address is pending verification.")
def validate_email(self, field): field.data = field.data.lower() # Convert to lowercase existing = UserEmail.get(email=field.data) if existing is not None: if existing.user == current_auth.user: raise forms.ValidationError( _("You have already registered this email address")) else: raise forms.ValidationError( _("This email address has already been claimed")) existing = UserEmailClaim.get_for(user=current_auth.user, email=field.data) if existing is not None: raise forms.ValidationError( _("This email address is pending verification"))
def profile_edit(newprofile=False): form = ProfileForm(obj=g.user) form.edit_user = g.user form.fullname.description = current_app.config.get('FULLNAME_REASON') form.email.description = current_app.config.get('EMAIL_REASON') form.username.description = current_app.config.get('USERNAME_REASON') form.description.description = current_app.config.get('BIO_REASON') form.timezone.description = current_app.config.get('TIMEZONE_REASON') if g.user.email or newprofile is False: del form.email if newprofile is True: del form.description if form.validate_on_submit(): # Can't auto-populate here because user.email is read-only g.user.fullname = form.fullname.data g.user.username = form.username.data if not newprofile: g.user.description = form.description.data g.user.timezone = form.timezone.data if newprofile and not g.user.email: useremail = UserEmailClaim.get(user=g.user, email=form.email.data) if useremail is None: useremail = UserEmailClaim(user=g.user, email=form.email.data) db.session.add(useremail) send_email_verify_link(useremail) db.session.commit() user_data_changed.send(g.user, changes=['profile', 'email-claim']) flash("Your profile has been updated. We sent you an email to confirm your address", category='success') else: db.session.commit() user_data_changed.send(g.user, changes=['profile']) flash("Your profile has been updated.", category='success') if newprofile: return render_redirect(get_next_url(), code=303) else: return render_redirect(url_for('profile'), code=303) if newprofile: return render_form(form, title="Update profile", formid="profile_new", submit="Continue", message=Markup(u"Hello, <strong>{fullname}</strong>. Please spare a minute to fill out your profile.".format( fullname=escape(g.user.fullname))), ajax=True) else: return render_form(form, title="Edit profile", formid="profile_edit", submit="Save changes", ajax=True)
def remove_email(md5sum): useremail = UserEmail.get_for(user=current_auth.user, md5sum=md5sum) if not useremail: useremail = UserEmailClaim.get_for(user=current_auth.user, md5sum=md5sum) if not useremail: abort(404) if isinstance(useremail, UserEmail) and useremail.primary: flash(_("You cannot remove your primary email address"), 'danger') return render_redirect(url_for('.account'), code=303) if request.method == 'POST': # FIXME: Confirm validation success user_data_changed.send(current_auth.user, changes=['email-delete']) return render_delete_sqla( useremail, db, title=_("Confirm removal"), message=_("Remove email address {email} from your account?").format( email=useremail.email), success=_("You have removed your email address {email}").format( email=useremail.email), next=url_for('.account'), delete_text=_("Remove"), )
def confirm_email(md5sum, secret): emailclaim = UserEmailClaim.get_by(md5sum=md5sum, verification_code=secret) if emailclaim is not None: if 'verify' in emailclaim.permissions(current_auth.user): existing = UserEmail.get(email=emailclaim.email) if existing is not None: claimed_email = emailclaim.email claimed_user = emailclaim.user db.session.delete(emailclaim) db.session.commit() if claimed_user != current_auth.user: return render_message( title=_("Email address already claimed"), message=Markup( _( "The email address <code>{email}</code> has already been verified by another user" ).format(email=escape(claimed_email)) ), ) else: return render_message( title=_("Email address already verified"), message=Markup( _( "Hello <strong>{fullname}</strong>! " "Your email address <code>{email}</code> has already been verified" ).format( fullname=escape(claimed_user.fullname), email=escape(claimed_email), ) ), ) useremail = emailclaim.user.add_email( emailclaim.email, primary=emailclaim.user.email is None, type=emailclaim.type, private=emailclaim.private, ) db.session.delete(emailclaim) UserEmailClaim.all(useremail.email).delete(synchronize_session=False) db.session.commit() user_data_changed.send(current_auth.user, changes=['email']) return render_message( title=_("Email address verified"), message=Markup( _( "Hello <strong>{fullname}</strong>! " "Your email address <code>{email}</code> has now been verified" ).format( fullname=escape(emailclaim.user.fullname), email=escape(useremail.email), ) ), ) else: return render_message( title=_("This was not for you"), message=_( "You’ve opened an email verification link that was meant for another user. " "If you are managing multiple accounts, please login with the correct account " "and open the link again" ), code=403, ) else: return render_message( title=_("Expired confirmation link"), message=_( "The confirmation link you clicked on is either invalid or has expired" ), code=404, )
def login_service_postcallback(service, userdata): """ Called from :func:login_service_callback after receiving data from the upstream login service """ # 1. Check whether we have an existing UserExternalId user, extid, useremail = get_user_extid(service, userdata) # If extid is not None, user.extid == user, guaranteed. # If extid is None but useremail is not None, user == useremail.user # However, if both extid and useremail are present, they may be different users if extid is not None: extid.oauth_token = userdata.get('oauth_token') extid.oauth_token_secret = userdata.get('oauth_token_secret') extid.oauth_token_type = userdata.get('oauth_token_type') extid.username = userdata.get('username') # TODO: Save refresh token and expiry date where present extid.oauth_refresh_token = userdata.get('oauth_refresh_token') extid.oauth_expiry_date = userdata.get('oauth_expiry_date') extid.oauth_refresh_expiry = userdata.get( 'oauth_refresh_expiry') # TODO: Check this extid.last_used_at = db.func.utcnow() else: # New external id. Register it. extid = UserExternalId( user=user, # This may be None right now. Will be handled below service=service, userid=userdata['userid'], username=userdata.get('username'), oauth_token=userdata.get('oauth_token'), oauth_token_secret=userdata.get('oauth_token_secret'), oauth_token_type=userdata.get('oauth_token_type'), last_used_at=db.func.utcnow() # TODO: Save refresh token ) if user is None: if current_auth: # Attach this id to currently logged-in user user = current_auth.user extid.user = user else: # Register a new user user = register_internal(None, userdata.get('fullname'), None) extid.user = user if userdata.get('username'): if valid_username(userdata['username']) and user.is_valid_name( userdata['username']): # Set a username for this user if it's available user.username = userdata['username'] else: # We have an existing user account from extid or useremail if current_auth and current_auth.user != user: # Woah! Account merger handler required # Always confirm with user before doing an account merger session['merge_buid'] = user.buid elif useremail and useremail.user != user: # Once again, account merger required since the extid and useremail are linked to different users session['merge_buid'] = useremail.user.buid # Check for new email addresses if userdata.get('email') and not useremail: user.add_email(userdata['email']) # If there are multiple email addresses, add any that are not already claimed. # If they are already claimed by another user, this calls for an account merge # request, but we can only merge two users at a time. Ask for a merge if there # isn't already one pending if userdata.get('emails'): for email in userdata['emails']: existing = UserEmail.get(email) if existing: if existing.user != user and 'merge_buid' not in session: session['merge_buid'] = existing.user.buid else: user.add_email(email) if userdata.get('emailclaim'): emailclaim = UserEmailClaim(user=user, email=userdata['emailclaim']) db.session.add(emailclaim) send_email_verify_link(emailclaim) # Is the user's fullname missing? Populate it. if not user.fullname and userdata.get('fullname'): user.fullname = userdata['fullname'] if not current_auth: # If a user isn't already logged in, login now. login_internal(user) flash( _("You have logged in via {service}").format( service=login_registry[service].title), 'success', ) next_url = get_next_url(session=True) db.session.add(extid) # If we made a new extid, add it to the session now db.session.commit() # Finally: set a login method cookie and send user on their way if not current_auth.user.is_profile_complete(): login_next = url_for('.account_new', next=next_url) else: login_next = next_url if 'merge_buid' in session: return set_loginmethod_cookie( redirect(url_for('.account_merge', next=login_next), code=303), service) else: return set_loginmethod_cookie(redirect(login_next, code=303), service)
def login_service_postcallback(service, userdata): user, extid, useremail = get_user_extid(service, userdata) if extid is not None: extid.oauth_token = userdata.get('oauth_token') extid.oauth_token_secret = userdata.get('oauth_token_secret') extid.oauth_token_type = userdata.get('oauth_token_type') extid.username = userdata.get('username') # TODO: Save refresh token and expiry date where present extid.oauth_refresh_token = userdata.get('oauth_refresh_token') extid.oauth_expiry_date = userdata.get('oauth_expiry_date') extid.oauth_refresh_expiry = userdata.get( 'oauth_refresh_expiry') # TODO: Check this else: # New external id. Register it. extid = UserExternalId( user=user, # This may be None right now. Will be handled below service=service, userid=userdata['userid'], username=userdata.get('username'), oauth_token=userdata.get('oauth_token'), oauth_token_secret=userdata.get('oauth_token_secret'), oauth_token_type=userdata.get('oauth_token_type') # TODO: Save refresh token ) db.session.add(extid) if user is None: if g.user: # Attach this id to currently logged-in user user = g.user extid.user = user else: # Register a new user user = register_internal(None, userdata.get('fullname'), None) extid.user = user if userdata.get('username'): if valid_username( userdata['username']) and user.is_valid_username( userdata['username']): # Set a username for this user if it's available user.username = userdata['username'] else: # This id is attached to a user if g.user and g.user != user: # Woah! Account merger handler required # Always confirm with user before doing an account merger session['merge_userid'] = user.userid # Check for new email addresses if userdata.get('email') and not useremail: user.add_email(userdata['email']) if userdata.get('emailclaim'): emailclaim = UserEmailClaim(user=user, email=userdata['emailclaim']) db.session.add(emailclaim) send_email_verify_link(emailclaim) # Is the user's fullname missing? Populate it. if not user.fullname and userdata.get('fullname'): user.fullname = userdata['fullname'] if not g.user: # If a user isn't already logged in, login now. login_internal(user) flash( u"You have logged in via {service}.".format( service=login_registry[service].title), 'success') next_url = get_next_url(session=True) db.session.commit() # Finally: set a login method cookie and send user on their way if not user.is_profile_complete(): login_next = url_for('.profile_new', next=next_url) else: login_next = next_url if 'merge_userid' in session: return set_loginmethod_cookie( redirect(url_for('.profile_merge', next=login_next), code=303), service) else: return set_loginmethod_cookie(redirect(login_next, code=303), service)