AIRAVATA-3647 Special display for unauthenticated errors, directing users to re-authenticate
diff --git a/django_airavata/apps/api/exceptions.py b/django_airavata/apps/api/exceptions.py
index 7caf17c..0676c12 100644
--- a/django_airavata/apps/api/exceptions.py
+++ b/django_airavata/apps/api/exceptions.py
@@ -4,6 +4,7 @@
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 @@
{'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 7832993..b1213e4 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 @@
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 fbde9a2..41ece6a 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 @@
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 35074fd..10d75b1 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 @@
}
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 8a52802..f11e7d1 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 @@
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 1954218..5d7a8f6 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 @@
}.bind(this);
setTimeout(pollAPIServerStatus.bind(this), this.pollingDelay);
},
+ isUnauthenticatedError(error) {
+ return errors.ErrorUtils.isUnauthenticatedError(error);
+ },
},
computed: {
apiServerDown() {
@@ -130,6 +153,12 @@
: false;
return notificationsApiServerDown || unhandledErrorsApiServerDown;
},
+ loginLinkWithNext() {
+ return errors.ErrorUtils.buildLoginUrl();
+ },
+ loginLink() {
+ return errors.ErrorUtils.buildLoginUrl(false);
+ },
},
watch: {
/*