From 704acabcf5e23591184f4923dab6587e94cacd58 Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Wed, 29 Mar 2023 14:25:41 +0100 Subject: [PATCH 1/3] Refactor Highlight to be a functional component, fix not re-rendering on content change --- Sample-01/src/components/Highlight.js | 92 ++++++--------------------- Sample-01/src/views/ExternalApi.js | 4 +- Sample-01/src/views/Profile.js | 2 +- 3 files changed, 23 insertions(+), 75 deletions(-) diff --git a/Sample-01/src/components/Highlight.js b/Sample-01/src/components/Highlight.js index 7bdecf31..f8a17dc9 100644 --- a/Sample-01/src/components/Highlight.js +++ b/Sample-01/src/components/Highlight.js @@ -1,73 +1,23 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; - -import hljs from "highlight.js"; -import "highlight.js/styles/monokai-sublime.css"; - -const registeredLanguages = {}; - -class Highlight extends Component { - constructor(props) { - super(props); - - this.state = { loaded: false }; - this.codeNode = React.createRef(); - } - - componentDidMount() { - const { language } = this.props; - - if (language && !registeredLanguages[language]) { - try { - const newLanguage = require(`highlight.js/lib/languages/${language}`); - hljs.registerLanguage(language, newLanguage); - registeredLanguages[language] = true; - - this.setState({ loaded: true }, this.highlight); - } catch (e) { - console.error(e); - throw Error(`Cannot register the language ${language}`); - } - } else { - this.setState({ loaded: true }); - } - } - - componentDidUpdate() { - this.highlight(); - } - - highlight = () => { - this.codeNode && - this.codeNode.current && - hljs.highlightBlock(this.codeNode.current); - }; - - render() { - const { language, children } = this.props; - const { loaded } = this.state; - - if (!loaded) { - return null; - } - - return ( -
-        
-          {children}
-        
-      
- ); - } -} - -Highlight.propTypes = { - children: PropTypes.node.isRequired, - language: PropTypes.string, -}; - -Highlight.defaultProps = { - language: "json", +import React, { useEffect, useRef } from 'react'; + +import hljs from 'highlight.js'; +import 'highlight.js/styles/monokai-sublime.css'; + +const Highlight = (props) => { + const { text, language = 'json' } = props; + + const codeNode = useRef(null); + useEffect(() => { + hljs.highlightElement(codeNode.current); + }, [text]); + + return ( +
+      
+        {text}
+      
+    
+ ); }; -export default Highlight; +export default Highlight; \ No newline at end of file diff --git a/Sample-01/src/views/ExternalApi.js b/Sample-01/src/views/ExternalApi.js index 4f3c1d51..03d1a06e 100644 --- a/Sample-01/src/views/ExternalApi.js +++ b/Sample-01/src/views/ExternalApi.js @@ -185,9 +185,7 @@ export const ExternalApiComponent = () => { {state.showResult && (
Result
- - {JSON.stringify(state.apiMessage, null, 2)} - +
)} diff --git a/Sample-01/src/views/Profile.js b/Sample-01/src/views/Profile.js index 9c85922a..cb29b423 100644 --- a/Sample-01/src/views/Profile.js +++ b/Sample-01/src/views/Profile.js @@ -24,7 +24,7 @@ export const ProfileComponent = () => { - {JSON.stringify(user, null, 2)} + ); From 462808c4e255cf4564e18fce658f5fadcd3ba7e0 Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Wed, 29 Mar 2023 14:35:12 +0100 Subject: [PATCH 2/3] Display errors from the API in the UI Closes #218 --- Sample-01/api-server.js | 45 ++++++++++++++++++++++-------- Sample-01/src/views/ExternalApi.js | 26 ++++++++++++++++- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/Sample-01/api-server.js b/Sample-01/api-server.js index 4e946daa..b7727f29 100644 --- a/Sample-01/api-server.js +++ b/Sample-01/api-server.js @@ -2,7 +2,13 @@ const express = require("express"); const cors = require("cors"); const morgan = require("morgan"); const helmet = require("helmet"); -const { auth } = require("express-oauth2-jwt-bearer"); +const { + auth, + InvalidTokenError, + InvalidRequestError, + InsufficientScopeError, + requiredScopes, +} = require("express-oauth2-jwt-bearer"); const authConfig = require("./src/auth_config.json"); const app = express(); @@ -11,11 +17,7 @@ const port = process.env.API_PORT || 3001; const appPort = process.env.SERVER_PORT || 3000; const appOrigin = authConfig.appOrigin || `http://localhost:${appPort}`; -if ( - !authConfig.domain || - !authConfig.audience || - authConfig.audience === "YOUR_API_IDENTIFIER" -) { +if (!authConfig.domain || !authConfig.audience || authConfig.audience === "YOUR_API_IDENTIFIER") { console.log( "Exiting: Please make sure that auth_config.json is in place and populated with valid domain and audience values" ); @@ -27,16 +29,35 @@ app.use(morgan("dev")); app.use(helmet()); app.use(cors({ origin: appOrigin })); -const checkJwt = auth({ - audience: authConfig.audience, - issuerBaseURL: `https://${authConfig.domain}/`, - algorithms: ["RS256"], -}); +app.use( + auth({ + audience: authConfig.audience, + issuerBaseURL: `https://${authConfig.domain}/`, + algorithms: ["RS256"], + }) +); -app.get("/api/external", checkJwt, (req, res) => { +app.get("/api/external", requiredScopes('admin'), (req, res) => { res.send({ msg: "Your access token was successfully validated!", }); }); +// Custom error handler that will turn the errors from express-oauth2-jwt-bearer into a JSON object +// for the UI to handle +app.use((err, req, res, next) => { + if ( + err instanceof InvalidTokenError || + err instanceof InvalidRequestError || + err instanceof InsufficientScopeError + ) { + return res.status(err.status).send({ + error: err.code, + message: err.message, + }); + } + + res.send(err); +}); + app.listen(port, () => console.log(`API Server listening on port ${port}`)); diff --git a/Sample-01/src/views/ExternalApi.js b/Sample-01/src/views/ExternalApi.js index 03d1a06e..882d7e28 100644 --- a/Sample-01/src/views/ExternalApi.js +++ b/Sample-01/src/views/ExternalApi.js @@ -10,8 +10,10 @@ export const ExternalApiComponent = () => { const [state, setState] = useState({ showResult: false, - apiMessage: "", + showApiError: false, error: null, + apiMessage: "", + apiError: null, }); const { @@ -64,12 +66,27 @@ export const ExternalApiComponent = () => { }, }); + if (!response.ok) { + const apiError = response.headers.get('content-type')?.includes('application/json') + ? JSON.stringify(await response.json(), null, 2) + : await response.text(); + setState({ + ...state, + showApiError: true, + showResult: false, + apiError + }); + return; + } + const responseData = await response.json(); setState({ ...state, showResult: true, apiMessage: responseData, + apiError: null, + showApiError: false }); } catch (error) { setState({ @@ -188,6 +205,13 @@ export const ExternalApiComponent = () => { )} + + {state.showApiError && ( +
+
Error
+ +
+ )} ); From 5778c17a02965eff4661e85c8e64a459dd8e6524 Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Wed, 29 Mar 2023 15:00:54 +0100 Subject: [PATCH 3/3] Remove requiredScopes usage --- Sample-01/api-server.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sample-01/api-server.js b/Sample-01/api-server.js index b7727f29..42db266d 100644 --- a/Sample-01/api-server.js +++ b/Sample-01/api-server.js @@ -6,8 +6,7 @@ const { auth, InvalidTokenError, InvalidRequestError, - InsufficientScopeError, - requiredScopes, + InsufficientScopeError } = require("express-oauth2-jwt-bearer"); const authConfig = require("./src/auth_config.json"); @@ -37,7 +36,7 @@ app.use( }) ); -app.get("/api/external", requiredScopes('admin'), (req, res) => { +app.get("/api/external", (req, res) => { res.send({ msg: "Your access token was successfully validated!", });