Have all error pages be rendered by Angular
Fixes #2198 Fixes https://www.pivotaltracker.com/story/show/135724483
This commit is contained in:
parent
0aa6e6cd58
commit
c06bba38de
13 changed files with 78 additions and 116 deletions
|
@ -440,7 +440,7 @@ def confirm_user_email(code):
|
||||||
code = EmailConfirmation.get(EmailConfirmation.code == code,
|
code = EmailConfirmation.get(EmailConfirmation.code == code,
|
||||||
EmailConfirmation.email_confirm == True)
|
EmailConfirmation.email_confirm == True)
|
||||||
except EmailConfirmation.DoesNotExist:
|
except EmailConfirmation.DoesNotExist:
|
||||||
raise DataModelException('Invalid email confirmation code.')
|
raise DataModelException('Invalid email confirmation code')
|
||||||
|
|
||||||
user = code.user
|
user = code.user
|
||||||
user.verified = True
|
user.verified = True
|
||||||
|
@ -449,7 +449,7 @@ def confirm_user_email(code):
|
||||||
new_email = code.new_email
|
new_email = code.new_email
|
||||||
if new_email and new_email != old_email:
|
if new_email and new_email != old_email:
|
||||||
if find_user_by_email(new_email):
|
if find_user_by_email(new_email):
|
||||||
raise DataModelException('E-mail address already used.')
|
raise DataModelException('E-mail address already used')
|
||||||
|
|
||||||
old_email = user.email
|
old_email = user.email
|
||||||
user.email = new_email
|
user.email = new_email
|
||||||
|
@ -465,10 +465,10 @@ def create_reset_password_email_code(email):
|
||||||
try:
|
try:
|
||||||
user = User.get(User.email == email)
|
user = User.get(User.email == email)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
raise InvalidEmailAddressException('Email address was not found.')
|
raise InvalidEmailAddressException('Email address was not found')
|
||||||
|
|
||||||
if user.organization:
|
if user.organization:
|
||||||
raise InvalidEmailAddressException('Organizations can not have passwords.')
|
raise InvalidEmailAddressException('Organizations can not have passwords')
|
||||||
|
|
||||||
code = EmailConfirmation.create(user=user, pw_reset=True)
|
code = EmailConfirmation.create(user=user, pw_reset=True)
|
||||||
return code
|
return code
|
||||||
|
|
|
@ -205,18 +205,18 @@ def render_page_template(name, route_data=None, **kwargs):
|
||||||
version_number = ' - ' + _get_version_number()
|
version_number = ' - ' + _get_version_number()
|
||||||
|
|
||||||
resp = make_response(render_template(name,
|
resp = make_response(render_template(name,
|
||||||
route_data=json.dumps(route_data),
|
route_data=route_data,
|
||||||
external_styles=external_styles,
|
external_styles=external_styles,
|
||||||
external_scripts=external_scripts,
|
external_scripts=external_scripts,
|
||||||
main_styles=add_cachebusters(main_styles),
|
main_styles=add_cachebusters(main_styles),
|
||||||
library_styles=add_cachebusters(library_styles),
|
library_styles=add_cachebusters(library_styles),
|
||||||
main_scripts=add_cachebusters(main_scripts),
|
main_scripts=add_cachebusters(main_scripts),
|
||||||
library_scripts=add_cachebusters(library_scripts),
|
library_scripts=add_cachebusters(library_scripts),
|
||||||
feature_set=json.dumps(features.get_features()),
|
feature_set=features.get_features(),
|
||||||
config_set=json.dumps(frontend_visible_config(app.config)),
|
config_set=frontend_visible_config(app.config),
|
||||||
oauth_set=json.dumps(get_oauth_config()),
|
oauth_set=get_oauth_config(),
|
||||||
scope_set=json.dumps(scopes.app_scopes(app.config)),
|
scope_set=scopes.app_scopes(app.config),
|
||||||
vuln_priority_set=json.dumps(PRIORITY_LEVELS),
|
vuln_priority_set=PRIORITY_LEVELS,
|
||||||
enterprise_logo=app.config.get('ENTERPRISE_LOGO_URL', ''),
|
enterprise_logo=app.config.get('ENTERPRISE_LOGO_URL', ''),
|
||||||
mixpanel_key=app.config.get('MIXPANEL_KEY', ''),
|
mixpanel_key=app.config.get('MIXPANEL_KEY', ''),
|
||||||
munchkin_key=app.config.get('MARKETO_MUNCHKIN_ID', ''),
|
munchkin_key=app.config.get('MARKETO_MUNCHKIN_ID', ''),
|
||||||
|
|
|
@ -11,7 +11,7 @@ from app import app, analytics, get_app_url, github_login, google_login, dex_log
|
||||||
from auth.process import require_session_login
|
from auth.process import require_session_login
|
||||||
from data import model
|
from data import model
|
||||||
from endpoints.common import common_login, route_show_if
|
from endpoints.common import common_login, route_show_if
|
||||||
from endpoints.web import render_page_template_with_routedata
|
from endpoints.web import index
|
||||||
from util.security.jwtutil import decode, InvalidTokenError
|
from util.security.jwtutil import decode, InvalidTokenError
|
||||||
from util.validation import generate_valid_usernames
|
from util.validation import generate_valid_usernames
|
||||||
|
|
||||||
|
@ -20,15 +20,17 @@ client = app.config['HTTPCLIENT']
|
||||||
oauthlogin = Blueprint('oauthlogin', __name__)
|
oauthlogin = Blueprint('oauthlogin', __name__)
|
||||||
|
|
||||||
|
|
||||||
def render_ologin_error(service_name,
|
def render_ologin_error(service_name, error_message=None, register_redirect=False):
|
||||||
error_message='Could not load user data. The token may have expired.'):
|
user_creation = bool(features.USER_CREATION and features.DIRECT_LOGIN)
|
||||||
user_creation = features.USER_CREATION and features.DIRECT_LOGIN
|
error_info = {
|
||||||
return render_page_template_with_routedata('ologinerror.html',
|
'reason': 'ologinerror',
|
||||||
service_name=service_name,
|
'service_name': service_name,
|
||||||
error_message=error_message,
|
'error_message': error_message or 'Could not load user data. The token may have expired',
|
||||||
service_url=get_app_url(),
|
'service_url': get_app_url(),
|
||||||
user_creation=user_creation)
|
'user_creation': user_creation,
|
||||||
|
'register_redirect': register_redirect,
|
||||||
|
}
|
||||||
|
return index('', error_info=error_info)
|
||||||
|
|
||||||
def get_user(service, token):
|
def get_user(service, token):
|
||||||
token_param = {
|
token_param = {
|
||||||
|
@ -81,7 +83,7 @@ def conduct_oauth_login(service, user_id, username, email, metadata={}):
|
||||||
message = message + "\nPlease log in with your username and password and "
|
message = message + "\nPlease log in with your username and password and "
|
||||||
message = message + "associate your %s account to use it in the future." % (service_name, )
|
message = message + "associate your %s account to use it in the future." % (service_name, )
|
||||||
|
|
||||||
return render_ologin_error(service_name, message)
|
return render_ologin_error(service_name, message, register_redirect=True)
|
||||||
|
|
||||||
except model.DataModelException as ex:
|
except model.DataModelException as ex:
|
||||||
return render_ologin_error(service_name, ex.message)
|
return render_ologin_error(service_name, ex.message)
|
||||||
|
|
|
@ -67,7 +67,7 @@ def internal_error_display():
|
||||||
@web.errorhandler(404)
|
@web.errorhandler(404)
|
||||||
@web.route('/404', methods=['GET'])
|
@web.route('/404', methods=['GET'])
|
||||||
def not_found_error_display(e = None):
|
def not_found_error_display(e = None):
|
||||||
resp = index('', error_code=404)
|
resp = index('', error_code=404, error_info=dict(reason='notfound'))
|
||||||
resp.status_code = 404
|
resp.status_code = 404
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@ -275,12 +275,6 @@ def dbrevision_health():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@web.route('/disclaimer', methods=['GET'])
|
|
||||||
@no_cache
|
|
||||||
def disclaimer():
|
|
||||||
return render_page_template_with_routedata('disclaimer.html')
|
|
||||||
|
|
||||||
|
|
||||||
@web.route('/robots.txt', methods=['GET'])
|
@web.route('/robots.txt', methods=['GET'])
|
||||||
def robots():
|
def robots():
|
||||||
robots_txt = make_response(render_template('robots.txt', baseurl=get_app_url()))
|
robots_txt = make_response(render_template('robots.txt', baseurl=get_app_url()))
|
||||||
|
@ -367,7 +361,7 @@ def confirm_repo_email():
|
||||||
try:
|
try:
|
||||||
record = model.repository.confirm_email_authorization_for_repo(code)
|
record = model.repository.confirm_email_authorization_for_repo(code)
|
||||||
except model.DataModelException as ex:
|
except model.DataModelException as ex:
|
||||||
return render_page_template_with_routedata('confirmerror.html', error_message=ex.message)
|
return index('', error_info=dict(reason='confirmerror', error_message=ex.message))
|
||||||
|
|
||||||
message = """
|
message = """
|
||||||
Your E-mail address has been authorized to receive notifications for repository
|
Your E-mail address has been authorized to receive notifications for repository
|
||||||
|
@ -390,7 +384,7 @@ def confirm_email():
|
||||||
try:
|
try:
|
||||||
user, new_email, old_email = model.user.confirm_user_email(code)
|
user, new_email, old_email = model.user.confirm_user_email(code)
|
||||||
except model.DataModelException as ex:
|
except model.DataModelException as ex:
|
||||||
return render_page_template_with_routedata('confirmerror.html', error_message=ex.message)
|
return index('', error_info=dict(reason='confirmerror', error_message=ex.message))
|
||||||
|
|
||||||
if new_email:
|
if new_email:
|
||||||
send_email_changed(user.username, old_email, new_email)
|
send_email_changed(user.username, old_email, new_email)
|
||||||
|
@ -706,6 +700,7 @@ def redirect_to_repository(namespace_name, repo_name, tag_name):
|
||||||
# - If the repository does exist (no access), 403
|
# - If the repository does exist (no access), 403
|
||||||
# > If the user is not a member of the namespace: 403
|
# > If the user is not a member of the namespace: 403
|
||||||
error_info = {
|
error_info = {
|
||||||
|
'reason': 'notfound',
|
||||||
'for_repo': True,
|
'for_repo': True,
|
||||||
'namespace_exists': namespace_exists,
|
'namespace_exists': namespace_exists,
|
||||||
'namespace': namespace_name,
|
'namespace': namespace_name,
|
||||||
|
|
|
@ -23,3 +23,8 @@
|
||||||
width: 225px;
|
width: 225px;
|
||||||
height: 205px;
|
height: 205px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-view-element h2 .fa-exclamation-triangle {
|
||||||
|
margin-right: 16px;
|
||||||
|
color: #D64456;
|
||||||
|
}
|
|
@ -214,7 +214,8 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP
|
||||||
|
|
||||||
// 404/403
|
// 404/403
|
||||||
.route('/:catchall', 'error-view')
|
.route('/:catchall', 'error-view')
|
||||||
.route('/:catch/:all', 'error-view');
|
.route('/:catch/:all', 'error-view')
|
||||||
|
.route('/:catch/:all/:things', 'error-view');
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
// Configure compile provider to add additional URL prefixes to the sanitization list. We use
|
// Configure compile provider to add additional URL prefixes to the sanitization list. We use
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
*/
|
*/
|
||||||
angular.module('quayPages').config(['pages', function(pages) {
|
angular.module('quayPages').config(['pages', function(pages) {
|
||||||
pages.create('error-view', 'error-view.html', ErrorViewCtrl, {
|
pages.create('error-view', 'error-view.html', ErrorViewCtrl, {
|
||||||
'title': '{{code}}',
|
'title': '{{info.error_message || "Error"}}',
|
||||||
'description': 'Error',
|
'description': 'Error',
|
||||||
'newLayout': false
|
'newLayout': false
|
||||||
});
|
});
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
function ErrorViewCtrl($scope, ApiService, $routeParams, UserService) {
|
function ErrorViewCtrl($scope, ApiService, $routeParams, $rootScope, UserService) {
|
||||||
$scope.info = window.__error_info;
|
$scope.info = window.__error_info;
|
||||||
$scope.code = window.__error_code || 404;
|
$scope.code = window.__error_code || 404;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,28 @@
|
||||||
<div class="error-view-element">
|
<div class="error-view-element">
|
||||||
<!-- 404 -->
|
<div ng-switch on="info.reason">
|
||||||
<div class="err404" ng-if="code == 404">
|
<!-- Confirmation error -->
|
||||||
|
<div ng-switch-when="confirmerror">
|
||||||
|
<h2><i class="fa fa-exclamation-triangle"></i> Confirmation Error</h2>
|
||||||
|
<h3>{{ info.error_message || 'There was an error confirming your e-mail address' }}</h3>
|
||||||
|
<div>
|
||||||
|
If you've received this error after trying to recover your account, please perform the recovery process again.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- OAuth login error -->
|
||||||
|
<div ng-switch-when="ologinerror">
|
||||||
|
<h2><i class="fa fa-exclamation-triangle"></i>{{ info.service_name }} login error</h2>
|
||||||
|
<h3 ng-if="info.error_message">{{ info.error_message }}</h3>
|
||||||
|
<div ng-if="info.user_creation && info.register_redirect">
|
||||||
|
To continue, please register using the <a href="/signin">registration form</a>.
|
||||||
|
You will be able to reassociate this {{ info.service_name }} account to your new Quay account in the user settings panel.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Otherwise -->
|
||||||
|
<div ng-switch-default>
|
||||||
|
<!-- 404 -->
|
||||||
|
<div class="err404" ng-if="code == 404">
|
||||||
<h2>404: Not Found</h2>
|
<h2>404: Not Found</h2>
|
||||||
<h3 ng-if="!info.for_repo && !info.namespace_exists">The resource you're looking for doesn't exist</h3>
|
<h3 ng-if="!info.for_repo && !info.namespace_exists">The resource you're looking for doesn't exist</h3>
|
||||||
<h3 ng-if="info && !info.namespace_exists">Namespace <strong>{{ info.namespace }}</strong> doesn't exist</h3>
|
<h3 ng-if="info && !info.namespace_exists">Namespace <strong>{{ info.namespace }}</strong> doesn't exist</h3>
|
||||||
|
@ -9,18 +31,18 @@
|
||||||
<img src="/static/img/40x/quay-logo-404.svg">
|
<img src="/static/img/40x/quay-logo-404.svg">
|
||||||
|
|
||||||
<h4 ng-if="!info.for_repo && !info.namespace_exists">
|
<h4 ng-if="!info.for_repo && !info.namespace_exists">
|
||||||
Return to the <a href="/">main page</a>
|
Return to the <a href="/">main page</a>
|
||||||
</h4>
|
</h4>
|
||||||
<h4 ng-if="info && !info.namespace_exists && info.namespace">
|
<h4 ng-if="info && !info.namespace_exists && info.namespace">
|
||||||
<a href="/organizations/new?namespace={{ info.namespace }}">Create this namespace</a> or return to the <a href="/">main page</a>
|
<a href="/organizations/new?namespace={{ info.namespace }}">Create this namespace</a> or return to the <a href="/">main page</a>
|
||||||
</h4>
|
</h4>
|
||||||
<h4 ng-if="info && info.for_repo && info.namespace_exists && info.namespace">
|
<h4 ng-if="info && info.for_repo && info.namespace_exists && info.namespace">
|
||||||
<a href="/new?namespace={{ info.namespace }}&name={{ info.repo_name }}">Create this repository</a> or return to the <a href="/">main page</a>
|
<a href="/new?namespace={{ info.namespace }}&name={{ info.repo_name }}">Create this repository</a> or return to the <a href="/">main page</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 403 -->
|
<!-- 403 -->
|
||||||
<div class="err403" ng-if="code == 403">
|
<div class="err403" ng-if="code == 403">
|
||||||
<h2>403: Unauthorized</h2>
|
<h2>403: Unauthorized</h2>
|
||||||
<h3 ng-if="!info.for_repo">You are not authorized to view this resource</h3>
|
<h3 ng-if="!info.for_repo">You are not authorized to view this resource</h3>
|
||||||
<h3 ng-if="info.for_repo">You are not authorized to view this repository</h3>
|
<h3 ng-if="info.for_repo">You are not authorized to view this repository</h3>
|
||||||
|
@ -29,5 +51,8 @@
|
||||||
|
|
||||||
<h4 ng-if="info.for_repo">Contact the admin of the <strong>{{ info.namespace }}</strong> namespace for access to the repository or you can return to the <a href="/">main page</a></h4>
|
<h4 ng-if="info.for_repo">Contact the admin of the <strong>{{ info.namespace }}</strong> namespace for access to the repository or you can return to the <a href="/">main page</a></h4>
|
||||||
<h4 ng-if="!info.for_repo">Return to the <a href="/">main page</a></h4>
|
<h4 ng-if="!info.for_repo">Return to the <a href="/">main page</a></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,6 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html ng-app="quay">
|
<html ng-app="quay">
|
||||||
<head>
|
<head>
|
||||||
|
<base href="/">
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -33,12 +35,12 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.__endpoints = {{ route_data|safe }}.paths;
|
window.__endpoints = {{ route_data|tojson|safe }}.paths;
|
||||||
window.__features = {{ feature_set|safe }};
|
window.__features = {{ feature_set|tojson|safe }};
|
||||||
window.__config = {{ config_set|safe }};
|
window.__config = {{ config_set|tojson|safe }};
|
||||||
window.__oauth = {{ oauth_set|safe }};
|
window.__oauth = {{ oauth_set|tojson|safe }};
|
||||||
window.__auth_scopes = {{ scope_set|safe }};
|
window.__auth_scopes = {{ scope_set|tojson|safe }};
|
||||||
window.__vuln_priority = {{ vuln_priority_set|safe }}
|
window.__vuln_priority = {{ vuln_priority_set|tojson|safe }}
|
||||||
window.__token = '{{ csrf_token() }}';
|
window.__token = '{{ csrf_token() }}';
|
||||||
|
|
||||||
{% if error_code %}
|
{% if error_code %}
|
||||||
|
@ -46,7 +48,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if error_info %}
|
{% if error_info %}
|
||||||
window.__error_info = {{ error_info|safe }};
|
window.__error_info = {{ error_info|tojson|safe }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Confirmation error · Quay</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h2>There was an error confirming your e-mail address.</h2>
|
|
||||||
|
|
||||||
{% if error_message %}
|
|
||||||
<div class="alert alert-danger">{{ error_message }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Docker Trademark Disclaimer · Quay</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
Quay is in no way affiliated with or sponsored by Docker, Inc. Docker, Docker logo and dotCloud are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries. Docker, Inc. and other parties may also have trademark rights in other terms used herein.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,9 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block added_meta %}
|
|
||||||
<base href="/">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
<title>Authorize {{ application.name }} · Quay</title>
|
<title>Authorize {{ application.name }} · Quay</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Error Logging in with {{ service_name }} · Quay</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body_content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h2 style="margin-bottom: 20px;">There was an error logging in with {{ service_name }}.</h2>
|
|
||||||
|
|
||||||
{% if error_message %}
|
|
||||||
<div class="co-alert co-alert-danger">{{ error_message }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if user_creation %}
|
|
||||||
<div>
|
|
||||||
Please register using the <a ng-href="{{ service_url }}/signin" target="_self">registration form</a> to continue.
|
|
||||||
You will be able to connect your account to your Quay account
|
|
||||||
in the user settings.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
Reference in a new issue