From f814f2f655e82b3aebaa5bb95f88e5555a912269 Mon Sep 17 00:00:00 2001 From: LionelB Date: Tue, 8 Sep 2020 13:19:09 +0200 Subject: [PATCH 1/2] fix: fix auth --- targets/frontend/src/hoc/UserProvider.js | 33 ++---- targets/frontend/src/hoc/getDisplayName.js | 4 + .../src/lib/auth/authTokenExchange.js | 5 +- targets/frontend/src/lib/auth/token.js | 105 ++++++++++-------- 4 files changed, 76 insertions(+), 71 deletions(-) create mode 100644 targets/frontend/src/hoc/getDisplayName.js diff --git a/targets/frontend/src/hoc/UserProvider.js b/targets/frontend/src/hoc/UserProvider.js index 432d466bb..a52184efd 100644 --- a/targets/frontend/src/hoc/UserProvider.js +++ b/targets/frontend/src/hoc/UserProvider.js @@ -1,11 +1,13 @@ import { decode } from "jsonwebtoken"; -import { getDisplayName } from "next/dist/next-server/lib/utils"; import PropTypes from "prop-types"; import React, { createContext } from "react"; -import { auth, getToken, setToken } from "src/lib/auth/token"; +import { getDisplayName } from "src/hoc/getDisplayName"; +import { auth } from "src/lib/auth/token"; import { request } from "src/lib/request"; -function withUserProvider(WrappedComponent) { +export const UserContext = createContext({}); + +export function withUserProvider(WrappedComponent) { return class extends React.Component { static displayName = `withUserProvider(${getDisplayName( WrappedComponent @@ -16,27 +18,18 @@ function withUserProvider(WrappedComponent) { }; static async getInitialProps(ctx) { - console.log( - "[withUserProvider] getInitialProps ", - ctx.pathname, - ctx.req ? "server" : "client", - getToken() ? "found token" : "no token" - ); - const token = await auth(ctx); - if (!getToken()) { - setToken(token); - } - - const tokenData = decode(getToken()); const componentProps = WrappedComponent.getInitialProps && (await WrappedComponent.getInitialProps(ctx)); - return { ...componentProps, tokenData }; + + return { + ...componentProps, + tokenData: token ? decode(token.jwt_token) : null, + }; } render() { - console.log("---- withUserProvider", this.props.tokenData); return ( @@ -46,9 +39,7 @@ function withUserProvider(WrappedComponent) { }; } -const UserContext = createContext({}); - -const ProvideUser = ({ children, tokenData }) => { +export const ProvideUser = ({ children, tokenData }) => { let user = null; if (tokenData) { const claims = tokenData["https://hasura.io/jwt/claims"]; @@ -86,5 +77,3 @@ ProvideUser.propTypes = { children: PropTypes.node.isRequired, tokenData: PropTypes.object, }; - -export { withUserProvider, UserContext }; diff --git a/targets/frontend/src/hoc/getDisplayName.js b/targets/frontend/src/hoc/getDisplayName.js new file mode 100644 index 000000000..7084780e2 --- /dev/null +++ b/targets/frontend/src/hoc/getDisplayName.js @@ -0,0 +1,4 @@ +// Gets the display name of a JSX component for dev tools +export function getDisplayName(Component) { + return Component.displayName || Component.name || "Component"; +} diff --git a/targets/frontend/src/lib/auth/authTokenExchange.js b/targets/frontend/src/lib/auth/authTokenExchange.js index c3fad6d66..d8829d093 100644 --- a/targets/frontend/src/lib/auth/authTokenExchange.js +++ b/targets/frontend/src/lib/auth/authTokenExchange.js @@ -73,7 +73,10 @@ export const authExchange = (ctx) => ({ forward }) => { // If it's expired and we aren't refreshing it yet, start refreshing it if (isExpired && !authPromise) { - authPromise = auth(ctx).then(({ jwt_token }) => jwt_token); + authPromise = auth(ctx).then(({ jwt_token }) => { + console.log("[ authExchange ]", { jwt_token }); + return jwt_token; + }); } const { key } = operation; diff --git a/targets/frontend/src/lib/auth/token.js b/targets/frontend/src/lib/auth/token.js index 1f076f713..4f8601796 100644 --- a/targets/frontend/src/lib/auth/token.js +++ b/targets/frontend/src/lib/auth/token.js @@ -3,67 +3,76 @@ import Router from "next/router"; import { request } from "../request"; import { setRefreshTokenCookie } from "./setRefreshTokenCookie"; -let token = null; let inMemoryToken; -function getToken() { - return token ? token.jwt_token : null; +export function getToken() { + return inMemoryToken ? inMemoryToken.jwt_token : null; } -function isTokenExpired() { - const expired = !token || Date.now() > new Date(token.jwt_token_expiry); +export function isTokenExpired() { + const expired = + !inMemoryToken || Date.now() > new Date(inMemoryToken.jwt_token_expiry); return expired; } -async function auth(ctx) { - if (!inMemoryToken) { - const cookieHeader = - ctx && ctx.req - ? { - Cookie: ctx.req.headers.cookie, - } - : {}; - try { - const tokenData = await request( - ctx && ctx.req - ? `${process.env.FRONTEND_URL}/api/refresh_token` - : "/api/refresh_token", - { - body: {}, - credentials: "include", - headers: { - "Cache-Control": "no-cache", - ...cookieHeader, - }, - mode: "same-origin", +export async function auth(ctx) { + console.log("[ auth ] "); + if (ctx.token) { + return ctx.token; + } + if (inMemoryToken) { + return inMemoryToken; + } + + const cookieHeader = + ctx && ctx.req + ? { + Cookie: ctx.req.headers.cookie, } - ); - // for ServerSide call, we need to set the Cookie header - // to update the refresh_token value - if (ctx && ctx.res) { - setRefreshTokenCookie(ctx.res, tokenData.refresh_token); + : {}; + try { + const tokenData = await request( + ctx && ctx.req + ? `${process.env.FRONTEND_URL}/api/refresh_token` + : "/api/refresh_token", + { + body: {}, + credentials: "include", + headers: { + "Cache-Control": "no-cache", + ...cookieHeader, + }, + mode: "same-origin", } + ); + // for ServerSide call, we need to set the Cookie header + // to update the refresh_token value + if (ctx && ctx.res) { + setRefreshTokenCookie(ctx.res, tokenData.refresh_token); + // we also store token in context (this is probably a bad idea b) + // to reuse it and avoid refresh token twice + ctx.token = tokenData; + return tokenData; + } else { + // if on client, we store token in memory inMemoryToken = { ...tokenData }; - } catch (error) { - console.error("[ auth ] refreshToken error ", { error }); + } + return inMemoryToken; + } catch (error) { + console.error("[ auth ] refreshToken error ", { error }); - if (ctx && ctx.req) { - ctx.res.writeHead(302, { Location: "/login" }); - ctx.res.end(); - } else { - Router.push("/login"); - } + // we are on server side and its response is not ended yet + if (ctx && ctx.res && !ctx.res.writableEnded) { + ctx.res.writeHead(302, { Location: "/login" }); + ctx.res.end(); + } else if (ctx && !ctx.req) { + // if we are on the client + Router.push("/login"); } + return Promise.reject(error); } - const jwt_token = inMemoryToken; - if (!jwt_token) { - Router.push("/login"); - } - return jwt_token; } -function setToken(tokenData) { - token = tokenData; +export function setToken(token) { + inMemoryToken = token ? { ...token } : null; } - -export { auth, getToken, isTokenExpired, setToken }; From b018d9fcd865a6402167851ebb0a876998fe73a3 Mon Sep 17 00:00:00 2001 From: LionelB Date: Tue, 8 Sep 2020 16:19:11 +0200 Subject: [PATCH 2/2] fix: review --- targets/frontend/src/lib/auth/token.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/frontend/src/lib/auth/token.js b/targets/frontend/src/lib/auth/token.js index 4f8601796..546d91141 100644 --- a/targets/frontend/src/lib/auth/token.js +++ b/targets/frontend/src/lib/auth/token.js @@ -74,5 +74,5 @@ export async function auth(ctx) { } export function setToken(token) { - inMemoryToken = token ? { ...token } : null; + inMemoryToken = token; }