From 50914f289dcfb3ec6b9e632d9734dff7dd58a15a Mon Sep 17 00:00:00 2001 From: Aditya Thakral Date: Fri, 2 Apr 2021 02:22:16 -0400 Subject: [PATCH] Hookup backend auth to the editor --- backend/main.py | 3 +- frontend/components/Login/authcontext.tsx | 75 ++++++++++++++++------- frontend/components/Login/loginbox.tsx | 16 +++-- frontend/pages/editor.tsx | 59 ++++++------------ 4 files changed, 87 insertions(+), 66 deletions(-) diff --git a/backend/main.py b/backend/main.py index 372f23d..a967899 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ import os DB_PATH = os.path.join(os.path.dirname(__file__), 'links.db') app = Flask(__name__) -auth = HTTPBasicAuth() +auth = HTTPBasicAuth('CustomBasic') users = { "admin": generate_password_hash("test"), @@ -62,6 +62,7 @@ def update_links(): cur.executemany('INSERT INTO links VALUES (?,?,?,?)', links) con.commit() data = regen_JSON() + # TODO: Trigger a rebuild of the frontend outfile = open('data.json', 'w') print(data, file=outfile) outfile.close() diff --git a/frontend/components/Login/authcontext.tsx b/frontend/components/Login/authcontext.tsx index 55ce0e7..ae094d7 100644 --- a/frontend/components/Login/authcontext.tsx +++ b/frontend/components/Login/authcontext.tsx @@ -1,40 +1,71 @@ import React, { useState, useContext, createContext } from "react"; -export interface AuthContextState { - loggedIn: boolean; - loginFailed: boolean; - login: (pass?: string) => void; +interface LoggedInState { + loggedIn: true; + headers: HeadersInit; + logout(): void; } -const DEFAULT_STATE: AuthContextState = { +interface LoggedOutState { + loggedIn: false; + login(password: string): Promise; +} + +export type AuthState = LoggedInState | LoggedOutState; + +const AuthContext = createContext({ loggedIn: false, - loginFailed: false, - login: () => console.error("No parent AuthContext found!"), -}; - -const AuthContext: React.Context = createContext( - DEFAULT_STATE -); - -const password = "bubbles"; + login: () => { + throw new Error("No parent AuthContext found!"); + }, +} as AuthState); export const AuthProvider: React.FC = (props) => { const [loggedIn, setLoggedIn] = useState(false); - const [loginFailed, setLoginFailed] = useState(false); + const [headers, setHeaders] = useState(); - function login(pass?: string): void { - if (pass === password) { + function logout() { + setLoggedIn(false); + setHeaders(undefined); + } + + async function login(password: string): Promise { + const username = process.env.NEXT_PUBLIC_EDITOR_USERNAME; + + if (!username) { + throw new Error( + "Missing NEXT_PUBLIC_EDITOR_USERNAME environment variable" + ); + } + + const newHeaders = { + Authorization: `CustomBasic ${btoa(`${username}:${password}`)}`, + }; + + const res = await fetch("/api/editor/links", { headers: newHeaders }); + + if (res.status === 200) { setLoggedIn(true); - setLoginFailed(false); + setHeaders(newHeaders); + return true; } else { - setLoggedIn(false); - setLoginFailed(true); + logout(); + return false; } } return ( - + ); }; -export const useAuth = () => useContext(AuthContext); +export function useAuth(): AuthState { + return useContext(AuthContext); +} diff --git a/frontend/components/Login/loginbox.tsx b/frontend/components/Login/loginbox.tsx index fcaba99..8681e0c 100644 --- a/frontend/components/Login/loginbox.tsx +++ b/frontend/components/Login/loginbox.tsx @@ -4,7 +4,8 @@ import { useAuth } from "components/Login/authcontext"; const LoginBox: React.FC = () => { const [password, setPassword] = useState(""); const [focused, setFocused] = useState(false); - const { login, loginFailed } = useAuth(); + const [loginFailed, setLoginFailed] = useState(false); + const auth = useAuth(); const passwordLabelClassName = `absolute inset-y-0 left-0 px-4 font-sans text-gray-600 ${ focused || password @@ -12,10 +13,17 @@ const LoginBox: React.FC = () => { : "" } transition-transform pointer-events-none`; - function handleSubmit(e: React.SyntheticEvent): void { + async function handleSubmit(e: React.SyntheticEvent) { e.preventDefault(); - login(password); - setPassword(""); + + if (!auth.loggedIn) { + const loginSuccessful = await auth.login(password); + + if (!loginSuccessful) { + setLoginFailed(true); + setPassword(""); + } + } } return ( diff --git a/frontend/pages/editor.tsx b/frontend/pages/editor.tsx index 8a73fdf..fffdb50 100644 --- a/frontend/pages/editor.tsx +++ b/frontend/pages/editor.tsx @@ -1,6 +1,5 @@ import Head from "next/head"; -import { GetStaticProps } from "next"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { AuthProvider, useAuth } from "components/Login/authcontext"; import LoginHead from "components/Login/loginhead"; import LoginBox from "components/Login/loginbox"; @@ -8,33 +7,6 @@ import Analytics from "components/Analytics"; import Editor from "components/Editor"; import { EditableLink } from "components/Editor/Link"; -export const getStaticProps: GetStaticProps = async () => { - // TODO: Fetch links here - - return { - props: { - data: [ - { - name: "dummlink1", - url: "www.helloworld.com", - clicks: 0, - active: true, - }, - { - name: "dummlink2", - url: "www.hiworld.com", - clicks: 0, - active: true, - }, - ], - }, // will be passed to the page component as props - // Next.js will attempt to re-generate the page: - // - When a request comes intype EditableLink = { - // - At most once every second - revalidate: 1, - }; -}; - const LoginScreen: React.FC = () => (
@@ -51,16 +23,25 @@ const LoginScreen: React.FC = () => (
); -interface EditorPageProps { - data: any; -} +const EditorPage: React.FC = () => { + const auth = useAuth(); + const [links, setLinks] = useState([]); -const EditorPage: React.FC = ({ data }) => { - const { loggedIn } = useAuth(); - const [links, setLinks] = useState(data ?? []); + useEffect(() => { + async function fetchLinks() { + if (!auth.loggedIn) { + return; + } - console.log({ links }); - return loggedIn ? ( + const res = await fetch("/api/editor/links", { headers: auth.headers }); + + setLinks(await res.json()); + } + + fetchLinks(); + }, [auth]); + + return auth.loggedIn ? ( <> @@ -70,10 +51,10 @@ const EditorPage: React.FC = ({ data }) => { ); }; -export default function EditorPageWrapper({ data }: any): JSX.Element { +export default function EditorPageWrapper(): JSX.Element { return ( - + ); }