You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2022/10/20 22:27:00 UTC
[airavata-django-portal] 01/04: AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit faeb92a062f0e3b881f5ce7a8be23109d48f6539
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Oct 7 12:20:14 2022 -0400
AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate
---
django_airavata/apps/api/exceptions.py | 6 +++
.../django_airavata_api/js/errors/ErrorUtils.js | 19 +++++++++
.../js/errors/UnhandledError.js | 6 +++
.../js/errors/UnhandledErrorDispatcher.js | 14 ++++++-
.../django_airavata_api/js/utils/FetchUtils.js | 4 +-
.../common/js/components/NotificationsDisplay.vue | 49 +++++++++++++++++-----
6 files changed, 86 insertions(+), 12 deletions(-)
diff --git a/django_airavata/apps/api/exceptions.py b/django_airavata/apps/api/exceptions.py
index 7caf17c7..0676c126 100644
--- a/django_airavata/apps/api/exceptions.py
+++ b/django_airavata/apps/api/exceptions.py
@@ -4,6 +4,7 @@ from airavata.api.error.ttypes import AuthorizationException
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
from rest_framework import status
+from rest_framework.exceptions import NotAuthenticated
from rest_framework.response import Response
from rest_framework.views import exception_handler
from thrift.Thrift import TException
@@ -43,6 +44,11 @@ def custom_exception_handler(exc, context):
{'detail': str(exc)},
status=status.HTTP_404_NOT_FOUND)
+ if isinstance(exc, NotAuthenticated):
+ log.debug("NotAuthenticated", exc_info=exc)
+ if response is not None:
+ response.data['is_authenticated'] = False
+
# Generic handler
if response is None:
log.error("API exception", exc_info=exc)
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
index 78329930..b1213e42 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorUtils.js
@@ -22,4 +22,23 @@ export default {
isNotFoundError(error) {
return this.isAPIException(error) && error.details.status === 404;
},
+ isUnauthenticatedError(error) {
+ return (
+ this.isAPIException(error) &&
+ [401, 403].includes(error.details.status) &&
+ "is_authenticated" in error.details.response &&
+ error.details.response.is_authenticated === false
+ );
+ },
+ buildLoginUrl(includeNextParameter = true) {
+ let loginUrl = "/auth/login";
+ if (includeNextParameter) {
+ let currentURL = window.location.pathname;
+ if (window.location.search) {
+ currentURL += window.location.search;
+ }
+ loginUrl += `?next=${encodeURIComponent(currentURL)}`;
+ }
+ return loginUrl;
+ },
};
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
index fbde9a25..41ece6ae 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledError.js
@@ -1,3 +1,5 @@
+import ErrorUtils from "./ErrorUtils";
+
let idSequence = 0;
class UnhandledError {
constructor({
@@ -15,6 +17,10 @@ class UnhandledError {
this.suppressLogging = suppressLogging;
this.createdDate = new Date();
}
+
+ get isUnauthenticatedError() {
+ return ErrorUtils.isUnauthenticatedError(this.error);
+ }
}
export default UnhandledError;
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
index 35074fd1..10d75b1b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/UnhandledErrorDispatcher.js
@@ -21,10 +21,22 @@ class UnhandledErrorDispatcher {
}
reportUnhandledError(unhandledError) {
+ // Ignore unauthenticated errors that have already been displayed
+ if (
+ unhandledError.isUnauthenticatedError &&
+ UnhandledErrorList.list.some((e) => e.isUnauthenticatedError)
+ ) {
+ return;
+ }
+
if (!unhandledError.suppressDisplay) {
UnhandledErrorList.add(unhandledError);
}
- if (!unhandledError.suppressLogging) {
+ if (
+ !unhandledError.suppressLogging &&
+ // Don't log unauthenticated errors
+ !unhandledError.isUnauthenticatedError
+ ) {
ErrorReporter.reportUnhandledError(unhandledError);
}
}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
index 8a52802b..f11e7d12 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
@@ -1,3 +1,4 @@
+import ErrorUtils from "../errors/ErrorUtils";
import UnhandledErrorDispatcher from "../errors/UnhandledErrorDispatcher";
import Cache from "./Cache";
@@ -266,7 +267,8 @@ export default {
if (showSpinner) {
decrementCount();
}
- if (!ignoreErrors) {
+ // Always report unauthenticated errors so user knows they need to re-authenticate
+ if (!ignoreErrors || ErrorUtils.isUnauthenticatedError(error)) {
this.reportError(error);
}
throw error;
diff --git a/django_airavata/static/common/js/components/NotificationsDisplay.vue b/django_airavata/static/common/js/components/NotificationsDisplay.vue
index 19542187..5d7a8f6d 100644
--- a/django_airavata/static/common/js/components/NotificationsDisplay.vue
+++ b/django_airavata/static/common/js/components/NotificationsDisplay.vue
@@ -1,16 +1,36 @@
<template>
<div id="notifications-display">
<transition-group name="fade" tag="div">
- <b-alert
- v-for="unhandledError in unhandledErrors"
- variant="danger"
- :key="unhandledError.id"
- show
- dismissible
- @dismissed="dismissUnhandledError(unhandledError)"
- >
- {{ unhandledError.message }}
- </b-alert>
+ <template v-for="unhandledError in unhandledErrors">
+ <b-alert
+ v-if="isUnauthenticatedError(unhandledError.error)"
+ variant="danger"
+ :key="unhandledError.id"
+ show
+ dismissible
+ @dismissed="dismissUnhandledError(unhandledError)"
+ >
+ Your login session has expired. Please
+ <b-link class="alert-link" :href="loginLinkWithNext"
+ >log in again</b-link
+ >. You can also
+ <b-link class="alert-link" :href="loginLink" target="_blank"
+ >login in a separate tab
+ <i class="fa fa-external-link-alt" aria-hidden="true"></i
+ ></b-link>
+ and then return to this tab and try again.
+ </b-alert>
+ <b-alert
+ v-else
+ variant="danger"
+ :key="unhandledError.id"
+ show
+ dismissible
+ @dismissed="dismissUnhandledError(unhandledError)"
+ >
+ {{ unhandledError.message }}
+ </b-alert>
+ </template>
<b-alert
v-for="notification in notifications"
:variant="variant(notification)"
@@ -86,6 +106,9 @@ export default {
}.bind(this);
setTimeout(pollAPIServerStatus.bind(this), this.pollingDelay);
},
+ isUnauthenticatedError(error) {
+ return errors.ErrorUtils.isUnauthenticatedError(error);
+ },
},
computed: {
apiServerDown() {
@@ -130,6 +153,12 @@ export default {
: false;
return notificationsApiServerDown || unhandledErrorsApiServerDown;
},
+ loginLinkWithNext() {
+ return errors.ErrorUtils.buildLoginUrl();
+ },
+ loginLink() {
+ return errors.ErrorUtils.buildLoginUrl(false);
+ },
},
watch: {
/*