From 8036853803bcff6b60f7cfad28d60380ea0bc054 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Thu, 5 Mar 2020 15:39:19 +0100 Subject: [PATCH] added history and auth0 provider --- README.md | 2 +- client/package-lock.json | 38 ++++++++++ client/package.json | 1 + client/src/authentication/auth0.tsx | 112 ++++++++++++++++++++++++++++ client/src/utils/history.ts | 2 + 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 client/src/authentication/auth0.tsx create mode 100644 client/src/utils/history.ts diff --git a/README.md b/README.md index 7a8d9ad..4980c9f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ - [ ] error page redirect when offline. - [x] ticket/files/activities list placeholders when empty - [ ] think about public/private DTO's constructor, getters and setters -- [X] write dtos without circular dependencies. +- [x] write dtos without circular dependencies. - [ ] use dtoRequest for PutProjects - [ ] render avatarlist after UserModal Update - [ ] Form validators diff --git a/client/package-lock.json b/client/package-lock.json index 2aff520..98c9d66 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4,6 +4,19 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@auth0/auth0-spa-js": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.6.4.tgz", + "integrity": "sha512-zKUfUxOHL/YwSedP1BkKKRRrB4AJ7whMKBVlf6M7dTvVzeCrRzTWEXUF+HB4/iG4skVRyr+U35qQzMYoFWCxhw==", + "requires": { + "browser-tabs-lock": "^1.2.1", + "core-js": "^3.2.1", + "es-cookie": "^1.2.0", + "fast-text-encoding": "^1.0.0", + "promise-polyfill": "^8.1.3", + "unfetch": "^4.1.0" + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -2702,6 +2715,11 @@ } } }, + "browser-tabs-lock": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.7.tgz", + "integrity": "sha512-2H3EX1YstZEI8mkmoKPo7cIUfc0lJe2nysFQjcY4wRk/Z6Mb6jnDPYEloQNP0LQuBe4J7M1T78+sFE0gZagHDw==" + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -4315,6 +4333,11 @@ "string.prototype.trimright": "^2.1.1" } }, + "es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -5171,6 +5194,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -10219,6 +10247,11 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, "prompts": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz", @@ -12549,6 +12582,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" }, + "unfetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", + "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/client/package.json b/client/package.json index b827484..45cf43b 100644 --- a/client/package.json +++ b/client/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@auth0/auth0-spa-js": "^1.6.4", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", diff --git a/client/src/authentication/auth0.tsx b/client/src/authentication/auth0.tsx new file mode 100644 index 0000000..ce8a219 --- /dev/null +++ b/client/src/authentication/auth0.tsx @@ -0,0 +1,112 @@ +import React, { useState, useEffect, useContext, FC } from "react"; +import createAuth0Client from "@auth0/auth0-spa-js"; +import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client"; + +interface IAuth0Context { + isAuthenticated: boolean; + user: any; + loading: boolean; + popupOpen: boolean; + loginWithPopup(options: PopupLoginOptions): Promise; + handleRedirectCallback(): Promise; + getIdTokenClaims(o?: getIdTokenClaimsOptions): Promise; + loginWithRedirect(o: RedirectLoginOptions): Promise; + getTokenSilently(o?: GetTokenSilentlyOptions): Promise; + getTokenWithPopup(o?: GetTokenWithPopupOptions): Promise; + logout(o?: LogoutOptions): void; +} +interface IAuth0ProviderOptions { + children: React.ReactElement; + onRedirectCallback?(result: RedirectLoginResult): void; +} + +const DEFAULT_REDIRECT_CALLBACK: () => void = () => + window.history.replaceState({}, document.title, window.location.pathname); + +export const Auth0Context: React.Context = React.createContext( + null +); +export const useAuth0: () => IAuth0Context = () => useContext(Auth0Context)!; +export const Auth0Provider = ({ + children, + onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, + ...initOptions +}: IAuth0ProviderOptions & Auth0ClientOptions) => { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [user, setUser] = useState(); + const [auth0Client, setAuth0] = useState(); + const [loading, setLoading] = useState(true); + const [popupOpen, setPopupOpen] = useState(false); + + useEffect(() => { + const initAuth0 = async () => { + const auth0FromHook = await createAuth0Client(initOptions); + setAuth0(auth0FromHook); + + if (window.location.search.includes("code=")) { + const { appState } = await auth0FromHook.handleRedirectCallback(); + onRedirectCallback(appState); + } + + const isAuthenticated = await auth0FromHook.isAuthenticated(); + + setIsAuthenticated(isAuthenticated); + + if (isAuthenticated) { + const user = await auth0FromHook.getUser(); + setUser(user); + } + + setLoading(false); + }; + initAuth0(); + // eslint-disable-next-line + }, []); + + const loginWithPopup = async (o: PopupLoginOptions) => { + setPopupOpen(true); + try { + await auth0Client!.loginWithPopup(o); + } catch (error) { + console.error(error); + } finally { + setPopupOpen(false); + } + const user = await auth0Client!.getUser(); + setUser(user); + setIsAuthenticated(true); + }; + + const handleRedirectCallback = async () => { + setLoading(true); + const result = await auth0Client!.handleRedirectCallback(); + const user = await auth0Client!.getUser(); + setLoading(false); + setIsAuthenticated(true); + setUser(user); + return result; + }; + return ( + + auth0Client!.getIdTokenClaims(o), + loginWithRedirect: (o: RedirectLoginOptions) => + auth0Client!.loginWithRedirect(o), + getTokenSilently: (o: GetTokenSilentlyOptions | undefined) => + auth0Client!.getTokenSilently(o), + getTokenWithPopup: (o: GetTokenWithPopupOptions | undefined) => + auth0Client!.getTokenWithPopup(o), + logout: (o: LogoutOptions | undefined) => auth0Client!.logout(o) + }} + > + {children} + + ); +}; diff --git a/client/src/utils/history.ts b/client/src/utils/history.ts new file mode 100644 index 0000000..6a454a1 --- /dev/null +++ b/client/src/utils/history.ts @@ -0,0 +1,2 @@ +import { createBrowserHistory } from "history"; +export default createBrowserHistory;