diff --git a/client/src/App.js b/client/src/App.js index 2000f5b1..a353fdf9 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -7,8 +7,10 @@ import Main from './containers/Main'; import { theme, MuiThemeProvider } from './components/elements'; export default () => ( - - + + @@ -18,6 +20,6 @@ export default () => ( - - + + ); diff --git a/client/src/components/elements/SpeciesSelect.js b/client/src/components/elements/SpeciesSelect.js index b44c8a31..88e33df1 100644 --- a/client/src/components/elements/SpeciesSelect.js +++ b/client/src/components/elements/SpeciesSelect.js @@ -7,7 +7,7 @@ import { useDataFetch } from '../../containers/Authenticate'; const SpeciesSelect = (props) => { const memoizedFetchFunc = useCallback( - (authorizedFetch) => + (fetchFn) => mockFetchOrNot( (mockFetch) => { return mockFetch.get('*', [ @@ -27,7 +27,7 @@ const SpeciesSelect = (props) => { ]); }, () => - authorizedFetch(`/api/species`, { + fetchFn(`/api/species`, { method: 'GET', }) ), diff --git a/client/src/containers/Authenticate/Profile.js b/client/src/containers/Authenticate/Profile.js index 37f57f6f..ca3b8deb 100644 --- a/client/src/containers/Authenticate/Profile.js +++ b/client/src/containers/Authenticate/Profile.js @@ -19,7 +19,6 @@ Profile.propTypes = { name: PropTypes.string.isRequired, email: PropTypes.string.isRequired, id: PropTypes.string.isRequired, - onLogout: PropTypes.func.isRequired, children: PropTypes.element, }; diff --git a/client/src/containers/Authenticate/TokenMgmt.js b/client/src/containers/Authenticate/TokenMgmt.js index 9f648412..4c856b9f 100644 --- a/client/src/containers/Authenticate/TokenMgmt.js +++ b/client/src/containers/Authenticate/TokenMgmt.js @@ -1,50 +1,114 @@ -import React, { useContext, useReducer } from 'react'; -import { Button } from '../../components/elements'; +import React, { useContext, useReducer, useEffect } from 'react'; import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Button } from '../../components/elements'; import AuthorizationContext from '../../containers/Authenticate/AuthorizationContext'; +import { useCallback } from 'react'; + +const ACTION_STORE = 'STORE'; +const ACTION_REVOKE = 'REVOKE'; +const UPDATE_METADATA = 'UPDATE_METADATA'; + +function tokenMetaDataReducer(state, action) { + let newState = { ...state }; + + switch (action.type) { + case UPDATE_METADATA: + console.log('Metadata update trigerred.'); + newState = { ...action['payload'] }; + break; + default: + console.log('Invalid action type detected:'); + console.log(action.type); + throw new Error(); + } -function TokenMgmt() { - const ACTION_STORE = 'STORE'; - const ACTION_REVOKE = 'REVOKE'; + return newState; +} + +function tokenReducer(state, action) { + const newState = { ...state }; + + switch (action.type) { + case ACTION_STORE: + newState['apiToken'] = action.payload; + break; + case ACTION_REVOKE: + newState['apiToken'] = null; + break; + default: + console.log('Invalid action type detected:'); + console.log(action.type); + throw new Error(); + } + return newState; +} + +function TokenMgmt() { const { authorizedFetch, user } = useContext(AuthorizationContext); const [tokenState, dispatchTokenState] = useReducer(tokenReducer, { apiToken: null, }); - const defaultTokenInstructions = - 'No stored ID token to display.\n' + - "Click the 'Store token' button below to store the current ID token and display it here."; - - function tokenReducer(state, action) { - const newState = { ...state }; - - switch (action.type) { - case ACTION_STORE: - newState['apiToken'] = user.id_token; - break; - case ACTION_REVOKE: - newState['apiToken'] = null; - break; - default: - console.log('Invalid action type detected:'); - console.log(action.type); - throw new Error(); + const [tokenMetaDataState, dispatchTokenMetaData] = useReducer( + tokenMetaDataReducer, + { + 'token-stored?': false, + 'last-used': null, } + ); - return newState; - } + const updateTokenMetadata = useCallback( + () => { + authorizedFetch('/api/auth/token-metadata', { method: 'GET' }) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + console.log( + 'Error while retrieving token metadata. Returned response: ', + response + ); + throw new Error('Error while retrieving token metadata'); + } + }) + .then((data) => { + console.log('token-metadata result received:', data); + + dispatchTokenMetaData({ type: UPDATE_METADATA, payload: data }); + }) + .catch((error) => { + console.log( + 'Error caught on authorizedFetch for token-metadata:', + error + ); + }); + }, + [authorizedFetch] + ); + + const noTokenInstructions = + 'No stored ID token to display.\n' + + "Click the 'Store token' button below to store the current ID token and display it here."; + const newTokenInstructions = + 'Stored tokens can not be retrieved for display after storage.\n' + + "Click the 'Store token' button below to store a new token (invalidating the current stored token) and display it here."; function storeTokenHandler() { console.log('storeTokenHandler triggered.'); authorizedFetch(`/api/auth/token`, { method: 'POST', + }).then((response) => { + if (response.ok) { + dispatchTokenState({ type: ACTION_STORE, payload: user.id_token }); + } else { + console.log('Error returned by /auth/token POST endpoint.'); + throw new Error('API endpoint for token storage returned error.'); + } }); - - dispatchTokenState({ type: ACTION_STORE }); } function revokeTokenHandler() { @@ -52,17 +116,41 @@ function TokenMgmt() { authorizedFetch(`/api/auth/token`, { method: 'DELETE', + }).then((response) => { + if (response.ok) { + dispatchTokenState({ type: ACTION_REVOKE }); + } else { + console.log('Error returned by /auth/token DELETE endpoint.'); + throw new Error('API endpoint for token revoking returned error.'); + } }); - - dispatchTokenState({ type: ACTION_REVOKE }); } + useEffect( + () => { + updateTokenMetadata(); + }, + [tokenState, updateTokenMetadata] + ); + return (
+ + Token stored?: {tokenMetaDataState['token-stored?'] ? 'Yes' : 'No'} + +
+ Token last used: {tokenMetaDataState['last-used'] || 'Never'} +