def verify_email_complete(): """View for concluding the login with Hydra after verifying an email address. The token ensures that we can only get here from the verify email view. """ token = request.args.get('token') try: login_request, challenge, brand, params = decode_token( token, 'account.verify_email_complete') form = AutoLoginForm() user_id = params.get('user_id') if request.method == 'POST': try: validate_auto_login(user_id) redirect_to = hydra_admin.accept_login_request( challenge, user_id) 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) return render_template('verify_email_complete.html', form=form, token=token, brand=brand) except x.HydraAdminError as e: return hydra_error_page(e)
def profile(): """View for updating user profile info.""" token = request.args.get('token') try: login_request, challenge, brand, params = decode_token( token, 'account.profile') user_id = params.get('user_id') user_info = get_user_profile(user_id) form = ProfileForm(**user_info) if request.method == 'POST': try: validate_auto_login(user_id) update_user_profile(user_id, **form.data) redirect_to = hydra_admin.accept_login_request( challenge, user_id) 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) return render_template('profile.html', form=form, token=token, brand=brand) 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 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 forgot_password(): """View for sending a password reset email. 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') form = ForgotPasswordForm() sent = False if request.method == 'POST': if form.validate(): email = form.email.data try: user_id = validate_forgot_password(email) name = get_user_profile_by_email(email)['name'] send_password_reset_email(email, name, challenge, brand) sent = True except x.ODPUserNotFound: form.email.errors.append("The email address is not associated with any user account.") 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) return redirect(redirect_to) return render_template('forgot_password.html', form=form, token=token, brand=brand, sent=sent) 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 logout(): """ Implements the logout provider component of the Hydra logout workflow. Hydra redirects to this endpoint based on the ``URLS_LOGOUT`` environment variable configured on the Hydra server. """ try: challenge = request.args.get('logout_challenge') logout_request = hydra_admin.get_logout_request(challenge) redirect_to = hydra_admin.accept_logout_request(challenge) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)
def consent(): """ Implements the consent provider component of the Hydra consent workflow. Hydra redirects to this endpoint based on the ``URLS_CONSENT`` environment variable configured on the Hydra server. """ challenge = request.args.get('consent_challenge') try: consent_request = hydra_admin.get_consent_request(challenge) user_id = consent_request['subject'] client_id = consent_request['client']['client_id'] try: user_permissions = get_user_permissions(user_id, client_id) user_info = get_user_info(user_id, client_id) grant_scope = [ requested_scope_id for requested_scope_id in consent_request['requested_scope'] if requested_scope_id in user_permissions or requested_scope_id not in ODPScope.__members__.values() ] consent_params = { 'grant_scope': grant_scope, 'grant_audience': consent_request['requested_access_token_audience'], 'access_token_data': user_permissions, 'id_token_data': asdict(user_info), } redirect_to = hydra_admin.accept_consent_request( challenge, **consent_params) return redirect(redirect_to) except x.ODPIdentityError as e: redirect_to = hydra_admin.reject_consent_request( challenge, e.error_code, e.error_description) return redirect(redirect_to) except x.HydraAdminError as e: return hydra_error_page(e)
def verify(): """View for sending a verification email. The token ensures that we can only get here from the user signup view. """ token = request.args.get('token') try: login_request, challenge, brand, params = decode_token(token, 'signup.verify') form = VerifyEmailForm() email = params.get('email') name = params.get('name') if request.method == 'POST': send_verification_email(email, name, challenge, brand) return render_template('signup_verify.html', form=form, token=token, brand=brand) except x.HydraAdminError as e: return hydra_error_page(e)
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)