diff --git a/components/MainLayout.js b/components/MainLayout.js index f2135e2..3965718 100644 --- a/components/MainLayout.js +++ b/components/MainLayout.js @@ -5,7 +5,6 @@ import MainLayoutFooter from './MainLayoutFooter' export default function MainLayout({ children }) { return (
- { children } diff --git a/components/MainLayoutNav.js b/components/MainLayoutNav.js index 7283142..5824528 100644 --- a/components/MainLayoutNav.js +++ b/components/MainLayoutNav.js @@ -1,4 +1,5 @@ import Link from 'next/link' +import { auth, logout } from '../lib/auth' export default function MainLayoutNav({ children }) { return ( @@ -42,7 +43,7 @@ export default function MainLayoutNav({ children }) {
-
+ { !auth &&
Login @@ -54,7 +55,36 @@ export default function MainLayoutNav({ children }) { Register -
+
} + + {auth &&
+
+
+ +
+ + {/*
+
+ + + + + {{ $t('BrandDefault.nav.logout') }} + +
*/} +
+
}
diff --git a/contexts/AuthContext.js b/contexts/AuthContext.js new file mode 100644 index 0000000..4fa7628 --- /dev/null +++ b/contexts/AuthContext.js @@ -0,0 +1,23 @@ +import React, { useState } from 'react' + +export const AuthContext = React.createContext() + +const defaultState = { + user: null, +} + +const AuthContextProvider = ({children}) => { + const [session, setSession] = useState(defaultState) + + function setUser(user) { + setSession({...session, user}) + } + + return ( + + {children} + + ) +} + +export default AuthContextProvider diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 0000000..c6d9612 --- /dev/null +++ b/lib/auth.js @@ -0,0 +1,78 @@ +import { useEffect, useContext } from 'react' +import Router from 'next/router' +import nextCookie from 'next-cookies' +import cookie from 'js-cookie' +import { isServer } from './isServer' + +export const setUserToken = (token) => { + cookie.set('token', token, { expires: 1 }) +} + +export const fetchUser = async (ctx) => { + const token = auth(ctx) + + const response = await fetch('http://api.leer-platform.test/v1/auth/user/current', { + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + token, + }, + }) + + const { data } = await response.json() + + return data +} + +export const auth = ctx => { + const { token } = nextCookie(ctx) + + if (!token) { + if (isServer()) { + ctx.res.writeHead(302, { Location: '/login' }) + ctx.res.end() + } else { + Router.push('/login') + } + } + + return token +} + +export const logout = () => { + cookie.remove('token') + // to support logging out from all windows + window.localStorage.setItem('logout', Date.now()) + Router.push('/login') +} + +export const withAuthSync = WrappedComponent => { + const AuthComponent = props => { + const syncLogout = event => { + if (event.key === 'logout') { + console.log('logged out from storage!') + Router.push('/login') + } + } + + useEffect(() => { + window.addEventListener('storage', syncLogout) + + return () => { + window.removeEventListener('storage', syncLogout) + window.localStorage.removeItem('logout') + } + }, []) + + return + } + + // AuthComponent.getInitialProps = async ctx => { + // const componentProps = + // WrappedComponent.getInitialProps && + // (await WrappedComponent.getInitialProps(ctx)) + + // return { ...componentProps } + // } + + return AuthComponent +} diff --git a/lib/isServer.js b/lib/isServer.js new file mode 100644 index 0000000..2298263 --- /dev/null +++ b/lib/isServer.js @@ -0,0 +1 @@ +export const isServer = typeof window === "undefined" diff --git a/package-lock.json b/package-lock.json index 83beb8d..70e2621 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1290,6 +1290,16 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "@types/object-assign": { + "version": "4.0.30", + "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz", + "integrity": "sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=" + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", @@ -1479,9 +1489,9 @@ }, "dependencies": { "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", "dev": true } } @@ -2277,6 +2287,11 @@ "safe-buffer": "~5.1.1" } }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -3805,6 +3820,11 @@ "supports-color": "^6.1.0" } }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -4332,6 +4352,14 @@ "webpack-sources": "1.4.3" } }, + "next-cookies": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/next-cookies/-/next-cookies-2.0.3.tgz", + "integrity": "sha512-YVCQzwZx+sz+KqLO4y9niHH9jjz6jajlEQbAKfsYVT6DOfngb/0k5l6vFK4rmpExVug96pGag8OBsdSRL9FZhQ==", + "requires": { + "universal-cookie": "^4.0.2" + } + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -6793,14 +6821,17 @@ } }, "tailwindcss": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.3.5.tgz", - "integrity": "sha512-hHGShfHBj7tAQRobnsYckDySPpMDnPF4KejHYYRcZjZQvyRRnCSHi2S905icK24HrYadOq9pZKwENqg2axSviw==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.4.6.tgz", + "integrity": "sha512-qV0qInUq1FWih39Bc5CWECdgObSzRrbjGD4ke4kAPSIq6WXrPhv0wwOcUWJgJ66ltT9j+XnSRYikG8WNRU/fTQ==", "dev": true, "requires": { + "@fullhuman/postcss-purgecss": "^2.1.2", "autoprefixer": "^9.4.5", + "browserslist": "^4.12.0", "bytes": "^3.0.0", "chalk": "^4.0.0", + "color": "^3.1.2", "detective": "^5.2.0", "fs-extra": "^8.0.0", "lodash": "^4.17.15", @@ -6816,14 +6847,61 @@ "resolve": "^1.14.2" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "@fullhuman/postcss-purgecss": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.2.0.tgz", + "integrity": "sha512-q4zYAn8L9olA5uneaLhxkHRBoug9dnAqytbdX9R5dbzSORobhYr1yGR2JN3Q1UMd5RB0apm1NvJekHaymal/BQ==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "postcss": "7.0.28", + "purgecss": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.28.tgz", + "integrity": "sha512-YU6nVhyWIsVtlNlnAj1fHTsUKW5qxm3KEgzq2Jj6KTEFOTK8QWR12eIDvrlWhiSTK8WIBFTBhOJV4DY6dUuEbw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + } + } + }, + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" } }, "chalk": { @@ -6834,6 +6912,33 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "color-convert": { @@ -6851,19 +6956,57 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "purgecss": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.2.1.tgz", + "integrity": "sha512-wngRSLW1dpNr8kr3TL9nTJMyTFI5BiRiaUUEys5M1CA4zEHLF25fRHoshEeDqmhstaNTOddmpYM34zRrUtEGbQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "commander": "^5.0.0", + "glob": "^7.0.0", + "postcss": "7.0.28", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.28.tgz", + "integrity": "sha512-YU6nVhyWIsVtlNlnAj1fHTsUKW5qxm3KEgzq2Jj6KTEFOTK8QWR12eIDvrlWhiSTK8WIBFTBhOJV4DY6dUuEbw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + } } } } @@ -7141,6 +7284,17 @@ "imurmurhash": "^0.1.4" } }, + "universal-cookie": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.3.tgz", + "integrity": "sha512-YbEHRs7bYOBTIWedTR9koVEe2mXrq+xdjTJZcoKJK/pQaE6ni28ak2AKXFpevb+X6w3iU5SXzWDiJkmpDRb9qw==", + "requires": { + "@types/cookie": "^0.3.3", + "@types/object-assign": "^4.0.30", + "cookie": "^0.4.0", + "object-assign": "^4.1.1" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index 98527ec..7409b50 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,16 @@ }, "dependencies": { "isomorphic-unfetch": "^3.0.0", + "js-cookie": "^2.2.1", "lodash": "^4.17.15", "next": "9.3.5", + "next-cookies": "^2.0.3", "react": "16.13.1", "react-dom": "16.13.1" }, "devDependencies": { "@fullhuman/postcss-purgecss": "^1.3.0", "postcss-preset-env": "^6.7.0", - "tailwindcss": "^1.3.4" + "tailwindcss": "^1.4.6" } } diff --git a/pages/_app.js b/pages/_app.js index 193f85e..c29cdf7 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,7 +1,12 @@ +import AuthContextProvider from '../contexts/AuthContext' import '../styles/index.css' function MyApp({ Component, pageProps }) { - return + return ( + + + + ) } export default MyApp diff --git a/pages/authenticated.js b/pages/authenticated.js new file mode 100644 index 0000000..127c725 --- /dev/null +++ b/pages/authenticated.js @@ -0,0 +1,29 @@ +import { useContext } from 'react' +import MainLayout from '../components/MainLayout' +import { fetchUser, withAuthSync } from '../lib/auth' +import { AuthContext } from '../contexts/AuthContext' + +function Authenticated({ user }) { + const { session, setUser } = useContext(AuthContext) + setUser(user) + + return ( + +
+
Authenticated with {session.user.name}
+
+
+ ) +} + +export async function getServerSideProps(ctx) { + const user = await fetchUser(ctx) + + return { + props: { + user, + }, + } +} + +export default withAuthSync(Authenticated) diff --git a/pages/login.js b/pages/login.js index 35dd54d..4064184 100644 --- a/pages/login.js +++ b/pages/login.js @@ -1,30 +1,99 @@ import MainLayout from '../components/MainLayout' +import { useState, useContext } from 'react'; +import fetch from 'isomorphic-unfetch' +import { setUserToken } from '../lib/auth' +import Router from 'next/router' + +const handleLogin = async (email, password) => { + const loginResponse = await fetch('http://api.leer-platform.test/v1/auth/user/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }) + + if (loginResponse.status !== 200) { + throw new Error(await loginResponse.text()) + } + + const { token } = await loginResponse.json() + setUserToken(token) + + Router.push('/authenticated') +} + +function Login({ cxt }) { + const [userData, setUserData] = useState({ + email: 'cyril@example.com', + password: 'secret', + error: '', + }) + + async function handleSubmit(event) { + event.preventDefault() + + setUserData({...userData, error: ''}) + + const email = userData.email + const password = userData.password + + try { + await handleLogin(email, password) + } catch (error) { + console.error(error) + setUserData({ ...userData, error: error.message }) + } + } -function Login() { return (
-
+ {userData.error &&

Error: {userData.error}

} + +
-
-
-