Skip to content
Merged
47 changes: 25 additions & 22 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ dependencies:
- daphne
- setuptools_scm
- pip
- requests>=2.32.4,<3 # required by lots of things
- requests>=2.32.4,<3 # required by lots of things
- tornado>=6.5,<7
- cryptography>=44.0.1,<45
- bcrypt # also required by channels, docker, daphne, condorpy
- bcrypt # also required by channels, docker, daphne, condorpy

# Gen CLI commands
- pyyaml
Expand All @@ -39,35 +39,37 @@ dependencies:
- django-model-utils
- django-guardian

######################################
# Optional Dependencies
######################################
######################################
# Optional Dependencies
######################################

# Security Plugins
- django-cors-headers # enable cors?
- django-session-security # session timeouts
- django-axes # tracked failed login attempts
- django-cors-headers # enable cors?
- django-session-security # session timeouts
- django-axes # tracked failed login attempts

# Login/Account Plugins
- django-gravatar2
- django-simple-captcha
- django-mfa2
- django-recaptcha
- social-auth-app-django
- hs_restclient # Used with HydroShare Social backend
- hs_restclient # Used with HydroShare Social backend
- python-jose # required by django-mfa2 - used for onelogin backend
- django-oauth-toolkit

# datetime dependencies for "humanize" template filter (used with MFA2)
- arrow
- isodate

# Misc Plugins
- django-termsandconditions # require users to accept terms and conditions
- django-cookie-consent # requires users to accept cookie usage
- django-analytical # track usage analytics
- django-json-widget # enable json widget for app settings
- djangorestframework # enable REST API framework
# Misc Plugins
- django-termsandconditions # require users to accept terms and conditions
- django-cookie-consent # requires users to accept cookie usage
- django-analytical # track usage analytics
- django-json-widget # enable json widget for app settings
- djangorestframework # enable REST API framework
- djangorestframework_simplejwt # JWT authentication for REST API
- djangorestframework_simplejwt[crypto] # JWT authentication with cryptography support

# Map Layout
- PyShp
Expand All @@ -81,9 +83,9 @@ dependencies:

# database dependencies
- postgresql
- psycopg2 # required by tethys_dataset_services
- sqlalchemy=1.* # TODO: what will it take to support sqlalchemy 2.0?
- geoalchemy2 # requires sqlalchemy
- psycopg2 # required by tethys_dataset_services
- sqlalchemy=1.* # TODO: what will it take to support sqlalchemy 2.0?
- geoalchemy2 # requires sqlalchemy

# plotting Gizmo dependencies
- plotly
Expand All @@ -95,11 +97,12 @@ dependencies:
- tethys_dask_scheduler>=1.0.2

# external services dependencies
- tethys_dataset_services>=2.0.0 # used with all data services
- owslib # used for creating WPS services
- siphon # used with Threads
- tethys_dataset_services>=2.0.0 # used with all data services
- owslib # used for creating WPS services
- siphon # used with Threads

# Docs

# Docs
- git

# tests/style dependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,53 @@
import PropTypes from 'prop-types';
import { useState, useEffect } from 'react';
import PropTypes from "prop-types";
import { useState, useEffect } from "react";

import tethysAPI from 'services/api/tethys';
import LoadingAnimation from 'components/loader/LoadingAnimation';
import { AppContext } from 'components/context';
import tethysAPI from "services/api/tethys";
import LoadingAnimation from "components/loader/LoadingAnimation";
import { AppContext } from "components/context";

const APP_ID = process.env.TETHYS_APP_ID;
const LOADER_DELAY = process.env.TETHYS_LOADER_DELAY;

function Loader({children}) {
function Loader({ children }) {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [appContext, setAppContext] = useState(null);

const handleError = (error) => {
// Delay setting the error to avoid flashing the loading animation
setTimeout(() => {
setError(error);
}, LOADER_DELAY);
};

useEffect(() => {
// Get the session first
tethysAPI.getSession()
.then(() => {
// Then load all other app data
Promise.all([
tethysAPI.getAppData(APP_ID),
tethysAPI.getUserData(),
tethysAPI.getCSRF(),
])
.then(([tethysApp, user, csrf]) => {
// Update app context
setAppContext({tethysApp, user, csrf});

// Allow for minimum delay to display loader
setTimeout(() => {
setIsLoaded(true)
}, LOADER_DELAY);
})
.catch(handleError);
}).catch(handleError);
useEffect(() => {
// load all other app data
Promise.all([
tethysAPI.getAppData(APP_ID),
tethysAPI.getUserData(),
tethysAPI.getJWTToken(),
])
.then(([tethysApp, user, jwt]) => {
// Update app context
setAppContext({ tethysApp, user, jwt });

// Allow for minimum delay to display loader
setTimeout(() => {
setIsLoaded(true);
}, LOADER_DELAY);
})
.catch(handleError);
}, []);

if (error) {
// Throw error so it will be caught by the ErrorBoundary
throw error;
} else if (!isLoaded) {
return (
<LoadingAnimation />
);
return <LoadingAnimation />;
} else {
return (
<>
<AppContext.Provider value={appContext}>
{children}
</AppContext.Provider>
<AppContext.Provider value={appContext}>{children}</AppContext.Provider>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
import apiClient from "services/api/client";

function getSession() {
return apiClient.get('/api/session/');
// JWT token storage helpers
const ACCESS_TOKEN_KEY = "jwt_access";
const REFRESH_TOKEN_KEY = "jwt_refresh";

export function setTokens(access, refresh) {
localStorage.setItem(ACCESS_TOKEN_KEY, access);
localStorage.setItem(REFRESH_TOKEN_KEY, refresh);
}
export function getAccessToken() {
return localStorage.getItem(ACCESS_TOKEN_KEY);
}
export function getRefreshToken() {
return localStorage.getItem(REFRESH_TOKEN_KEY);
}

async function getJWTToken() {
const response = await apiClient.get("/api/token/", {});
const access = response.access;
const refresh = response.refresh;
setTokens(access, refresh);
return { access, refresh };
}

function getCSRF() {
return apiClient.get('/api/csrf/')
.then(response => {
return response.headers['x-csrftoken'];
});
async function refreshJWTToken() {
const response = await apiClient.post("/api/token/refresh/", {
refresh: getRefreshToken(),
});
return response.data.access;
}

function getUserData() {
return apiClient.get('/api/whoami/');
return apiClient.get("/api/whoami/");
}

function getAppData(tethys_app_url) {
return apiClient.get(`/api/apps/${tethys_app_url}/`);
}

const tethysAPI = {
getSession,
getCSRF,
getJWTToken,
refreshJWTToken,
getAppData,
getUserData,
};

export default tethysAPI;
export default tethysAPI;
3 changes: 3 additions & 0 deletions tethys_portal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.TokenAuthentication",
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
}

Expand Down
13 changes: 11 additions & 2 deletions tethys_portal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
PasswordResetConfirmView,
PasswordResetCompleteView,
)
from rest_framework_simplejwt.views import (
TokenRefreshView,
TokenVerifyView,
)

from tethys_apps.urls import extension_urls

Expand Down Expand Up @@ -158,9 +162,14 @@
]

api_urls = [
re_path(r"^csrf/$", tethys_portal_api.get_csrf, name="get_csrf"),
re_path(r"^session/$", tethys_portal_api.get_session, name="get_session"),
re_path(r"^whoami/$", tethys_portal_api.get_whoami, name="get_whoami"),
re_path(
r"^token/$",
tethys_portal_api.get_token,
name="token_obtain_pair",
),
re_path(r"^token/refresh/$", TokenRefreshView.as_view(), name="token_refresh"),
re_path(r"^token/verify/$", TokenVerifyView.as_view(), name="token_verify"),
re_path(r"^apps/(?P<app>[\w-]+)/$", tethys_portal_api.get_app, name="get_app"),
]

Expand Down
Loading
Loading