diff --git a/web/components/private_route.tsx b/web/components/private_route.tsx index 82289dd..e32de7f 100644 --- a/web/components/private_route.tsx +++ b/web/components/private_route.tsx @@ -8,43 +8,32 @@ export type AuthProps = { auth: AuthToken } -type AuthState = { - auth: AuthToken -} - export function privateRoute(WrappedComponent: any) { - return class extends Component { - state = { - auth: new AuthToken(this.props.auth.token), - }; - + return class extends Component { static async getInitialProps(ctx: any) { const token = ServerCookie(ctx)[COOKIES.authToken]; const auth = new AuthToken(token); const initialProps = { auth }; + if (auth.isExpired) { + ctx.res.writeHead(302, { + Location: "/login?redirected=true", + }); + ctx.res.end(); + } if (WrappedComponent.getInitialProps) return WrappedComponent.getInitialProps(initialProps); return initialProps; } - componentDidMount(): void { - if (this.props.auth.decodedToken.exp === 0 || this.state.auth.isExpired) { - Router.push("/login"); - } - } - - componentDidUpdate(): void { - if (this.props.auth.decodedToken.exp === 0 || this.state.auth.isExpired) { - Router.push("/login"); - } - } - - render() { + get auth() { // the server pass to the client serializes the token // so we have to reinitialize the authToken class // // @see https://github.com/zeit/next.js/issues/3536 - // const auth =; - return ; + return new AuthToken(this.props.auth.token); + } + + render() { + return ; } }; } diff --git a/web/package-lock.json b/web/package-lock.json index c3501bb..89c7a7f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -2674,7 +2674,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2692,11 +2693,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2709,15 +2712,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2820,7 +2826,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2830,6 +2837,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2842,17 +2850,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2869,6 +2880,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2941,7 +2953,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2951,6 +2964,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3026,7 +3040,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3056,6 +3071,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3073,6 +3089,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3111,11 +3128,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/web/pages/dashboard.tsx b/web/pages/dashboard.tsx index caff8ff..698ce6b 100644 --- a/web/pages/dashboard.tsx +++ b/web/pages/dashboard.tsx @@ -3,19 +3,48 @@ import { AuthProps, privateRoute } from "../components/private_route"; import Cookie from "js-cookie"; import Router from "next/router"; import { COOKIES } from "../services/login_service"; +import { Links } from "../components/links"; +import { get } from "../services/rest_service"; +import { AuthToken } from "../services/auth_token"; -function Page(props: AuthProps) { +type Props = AuthProps & { + message: string +} + +function Page(props: Props) { const logout = async () => { Cookie.remove(COOKIES.authToken); alert("logout"); await Router.push("/login"); }; + console.log(props.auth); + return <> -

Hello Dashboard

-

{JSON.stringify(props.auth)}

+ +

{props.message}

+ {/*
{props.auth.authorizationString}
*/} + {props.auth.isAuthenticated ? "YES" : "NO"} } +Page.getInitialProps = async ({ auth }: AuthProps): Promise => { + const res: any = await get("/api/restricted", { + headers: { + Authorization: auth.authorizationString + } + }); + + let message = "Something unexpected happened!"; + + if (res.error) { + message = res.error; + } else if (res.data && res.data.message) { + message = res.data.message + } + + return { message, auth, }; +}; + export default privateRoute(Page); diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 72791e6..f8e6ee2 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -1,10 +1,32 @@ import React from "react"; import { Links } from "../components/links"; +import { get } from "../services/rest_service"; -function Page() { +type Props = { + message: string; +} + +function Page({message}: Props) { return <> +

The following is a result of a server side api call pre-render. If you right click and view source, the response from the API call will be visible in the source. This is different than say... Inspect Element, which shows the client side rendered content.

+

This means that search engines can scrape this page, and immediately see the content, without trusting that the search engines can render SPA's.

+

API Call: {message}

; } +Page.getInitialProps = async () => { + const res: any = await get("/api/unrestricted"); + + let message = "Something unexpected happened!"; + + if (res.error) { + message = res.error; + } else if (res.data && res.data.message) { + message = res.data.message + } + + return { message }; +}; + export default Page; diff --git a/web/pages/login.tsx b/web/pages/login.tsx index 4297c70..87ff436 100644 --- a/web/pages/login.tsx +++ b/web/pages/login.tsx @@ -9,7 +9,7 @@ export type LoginInputs = { function Page() { // these values are hardcoded since our main.go api only accepts this auth combo - const initialValues: LoginInputs = { email: "rickety_cricket@example.com", password: "shhh!", }; + const initialValues: LoginInputs = { email: "", password: "", }; const [inputs, setInputs] = useState(initialValues); const [error, setError] = useState(""); diff --git a/web/services/login_service.ts b/web/services/login_service.ts index f43dc42..0c92cfa 100644 --- a/web/services/login_service.ts +++ b/web/services/login_service.ts @@ -3,6 +3,7 @@ import Cookie from "js-cookie"; import Router from "next/router"; import { LoginInputs } from "../pages/login"; import { catchAxiosError } from "./error"; +import { post } from "./rest_service"; export const COOKIES = { authToken: "myApp.authToken" @@ -10,10 +11,7 @@ export const COOKIES = { export async function login(inputs: LoginInputs): Promise { const data = new URLSearchParams(inputs); - const config: AxiosRequestConfig = { - baseURL: "http://localhost:1323", - }; - const res: any = await axios.post("/api/login", data, config).catch(catchAxiosError); + const res: any = await post("/api/login", data).catch(catchAxiosError); if (res.error) { return res.error; } else if (!res.data || !res.data.token) { diff --git a/web/services/rest_service.ts b/web/services/rest_service.ts new file mode 100644 index 0000000..b4067ee --- /dev/null +++ b/web/services/rest_service.ts @@ -0,0 +1,19 @@ +import axios, { AxiosRequestConfig } from "axios"; +import { catchAxiosError } from "./error"; + + +const baseConfig: AxiosRequestConfig = { + baseURL: "http://localhost:1323", +}; + +export const post = (url: string, data: URLSearchParams) => { + return axios.post(url, data, baseConfig).catch(catchAxiosError); +}; + +export const get = async (url: string, config: AxiosRequestConfig = {}) => { + const axiosConfig = { + ...baseConfig, + ...config, + }; + return await axios.get(url, axiosConfig).catch(catchAxiosError) +};