def verify_email(): """Target for email verification links. The token ensures that this view is only accessible from a verification email. """ token = request.args.get('token') try: login_request, challenge, brand, params = decode_token( token, 'account.verify_email') email = params.get('email') try: user_id = validate_email_verification(email) update_user_verified(user_id, True) flash("Your email address has been verified.") complete_token = encode_token('account.verify_email_complete', challenge, brand, user_id=user_id) redirect_to = url_for('.verify_email_complete', token=complete_token) except x.ODPIdentityError as e: # any validation error => reject login redirect_to = hydra_admin.reject_login_request( challenge, e.error_code, e.error_description) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)
def login(): """ Implements the login provider component of the Hydra login workflow. Hydra redirects to this endpoint based on the ``URLS_LOGIN`` environment variable configured on the Hydra server. """ try: challenge = request.args.get('login_challenge') login_request = hydra_admin.get_login_request(challenge) mode = LoginMode.from_login_request(login_request) brand = Brand.from_login_request(login_request).value if mode == LoginMode.LOGIN: target_endpoint = 'login.login' elif mode == LoginMode.SIGNUP: target_endpoint = 'signup.signup' else: raise ValueError token = encode_token('login', challenge, brand) redirect_to = url_for(target_endpoint, token=token) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)
def signup(): """User signup view. The token ensures that we can only access this view in the context of the Hydra login workflow. """ token = request.args.get('token') try: # the token scope 'login' here is correct - it enables us to easily # switch between login and signup using the same token login_request, challenge, brand, params = decode_token(token, 'login') form = SignupForm() try: if request.method == 'GET': # if the user is already authenticated with Hydra, their user id is # associated with the login challenge; we cannot then associate a new # user id with the same login challenge authenticated = login_request['skip'] if authenticated: raise x.ODPSignupAuthenticatedUser else: # POST if form.validate(): email = form.email.data password = form.password.data name = form.name.data try: create_user_account(email, password, name) # the signup (and login) is completed via email verification send_verification_email(email, name, challenge, brand) verify_token = encode_token('signup.verify', challenge, brand, email=email, name=name) redirect_to = url_for('.verify', token=verify_token) return redirect(redirect_to) except x.ODPEmailInUse: form.email.errors.append("The email address is already associated with a user account.") except x.ODPPasswordComplexityError: form.password.errors.append("The password does not meet the minimum complexity requirements.") flash(password_complexity_description(), category='info') return render_template('signup.html', form=form, token=token, brand=brand, enable_google=config.GOOGLE.ENABLE) except x.ODPIdentityError as e: # any other validation error (e.g. user already authenticated) => reject login redirect_to = hydra_admin.reject_login_request(challenge, e.error_code, e.error_description) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)
def reset_password(): """Target for password reset links. The token ensures that this view is only accessible from a password reset email. """ token = request.args.get('token') try: login_request, challenge, brand, params = decode_token( token, 'account.reset_password') form = ResetPasswordForm() email = params.get('email') if request.method == 'POST': if form.validate(): password = form.password.data redirect_to = None try: user_id = validate_password_reset(email, password) update_user_password(user_id, password) flash("Your password has been changed.") complete_token = encode_token( 'account.reset_password_complete', challenge, brand, user_id=user_id) redirect_to = url_for('.reset_password_complete', token=complete_token) except x.ODPPasswordComplexityError: form.password.errors.append( "The password does not meet the minimum complexity requirements." ) flash(password_complexity_description(), category='info') except x.ODPIdentityError as e: # any other validation error => reject login redirect_to = hydra_admin.reject_login_request( challenge, e.error_code, e.error_description) if redirect_to: return redirect(redirect_to) return render_template('reset_password.html', form=form, token=token, brand=brand) except x.HydraAdminError as e: return hydra_error_page(e)
def send_password_reset_email(email, name, challenge, brand): """Send a password reset email. :param email: the email address :param name: the user's full name :param challenge: the Hydra login challenge :param brand: branding identifier """ try: token = encode_token('account.reset_password', challenge, brand, email=email) context = { 'url': url_for('account.reset_password', token=token, _external=True), 'name': name or '', 'brand': brand, } msg = Message( subject=render_template('email/reset_password_subject.txt', **context), body=render_template('email/reset_password.txt', **context), html=render_template('email/reset_password.html', **context), recipients=[email], sender=("SAEON", "*****@*****.**"), reply_to=("SAEON", "*****@*****.**"), ) mail.send(msg) flash("A password reset link has been sent to your email address.") except Exception as e: current_app.logger.error( "Error sending password reset email to {}: {}".format(email, e)) flash("There was a problem sending the password reset email.", category='error')
def login(): """User login view. The token ensures that we can only access this view in the context of the Hydra login workflow. """ token = request.args.get('token') try: login_request, challenge, brand, params = decode_token(token, 'login') user_id = None error = None form = LoginForm() if request.method == 'GET': authenticated = login_request['skip'] # indicates whether the user is already authenticated with Hydra # if already authenticated, we'll wind up with either a user_id or an error if authenticated: user_id = login_request['subject'] try: validate_auto_login(user_id) except x.ODPIdentityError as e: # any validation error => reject login user_id = None error = e # if not authenticated, we'll display the login form else: # POST if form.validate(): email = form.email.data password = form.password.data try: user_id = validate_user_login(email, password) except x.ODPUserNotFound: form.email.errors.append("The email address is not associated with any user account.") except x.ODPNoPassword: form.email.errors.append("Please click the 'Log in via Google' button.") except x.ODPIncorrectPassword: form.email.errors.append("The email address and password do not match.") except x.ODPEmailNotVerified: # the login is completed via email verification name = get_user_profile_by_email(email)['name'] send_verification_email(email, name, challenge, brand) verify_token = encode_token('login.verify', challenge, brand, email=email, name=name) return redirect(url_for('.verify', token=verify_token)) except x.ODPIdentityError as e: # any other validation error (e.g. account locked/disabled) => reject login error = e if user_id: redirect_to = hydra_admin.accept_login_request(challenge, user_id) elif error: redirect_to = hydra_admin.reject_login_request(challenge, error.error_code, error.error_description) else: return render_template('login.html', form=form, token=token, brand=brand, enable_google=config.GOOGLE.ENABLE) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)