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;