From f8fc373f2db45b326ffd511d227c3a23e01add89 Mon Sep 17 00:00:00 2001 From: Daven Quinn Date: Sun, 17 Nov 2024 18:18:05 -0600 Subject: [PATCH] Put column builder behind admin authorization --- pages/_error/+Page.ts | 43 ++++++++++++++++---- pages/dev/column-editor/+guard.ts | 10 +++++ pages/dev/me/+Page.client.ts | 7 ++++ pages/integrations/xdd/types/+Page.client.ts | 1 - server/vike-handler.ts | 13 +++--- vite.config.ts | 7 +++- 6 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 pages/dev/column-editor/+guard.ts create mode 100644 pages/dev/me/+Page.client.ts diff --git a/pages/_error/+Page.ts b/pages/_error/+Page.ts index 49c057cc..0240169b 100644 --- a/pages/_error/+Page.ts +++ b/pages/_error/+Page.ts @@ -2,23 +2,31 @@ import h from "@macrostrat/hyper"; import { CenteredContentPage } from "~/layouts"; import { PageHeader } from "~/components"; import { usePageContext } from "vike-react/usePageContext"; +import { ClientOnly } from "vike-react/ClientOnly"; +import { Spinner, Button } from "@blueprintjs/core"; export function Page() { + return h(CenteredContentPage, [h(PageHeader), h(PageContent)]); +} + +function PageContent() { const ctx = usePageContext(); const is404 = ctx.is404; + const path = ctx.urlPathname; + const statusCode = ctx.abortStatusCode; + const reason = ctx.abortReason; - return h(CenteredContentPage, [ - h(PageHeader, { title: "Macrostrat" }), - h(PageContent, { is404, path: ctx.urlPathname }), - ]); -} - -function PageContent({ is404, path }: { is404: boolean; path: string }) { if (is404) { return h([ h("h1", [h("code.bp5-code", "404"), " Page Not Found"]), h("p", ["Could not find a page at path ", h("code.bp5-code", path), "."]), ]); + } else if (statusCode == 401) { + return h([ + h("h1", [h("code.bp5-code", "401"), " Unauthorized"]), + h("p", [reason]), + h(LoginButton), + ]); } else { return h([ h("h1", "Internal Error"), @@ -27,3 +35,24 @@ function PageContent({ is404, path }: { is404: boolean; path: string }) { ]); } } + +function LoginButton() { + /** For now, the login button only loads on the client side */ + return h(ClientOnly, { + load: async () => { + const res = await import("@macrostrat/auth-components"); + return res.AuthStatus; + }, + fallback: h( + Button, + { + disabled: true, + icon: h(Spinner, { size: 16 }), + minimal: true, + large: true, + }, + "Not logged in" + ), + children: (component) => h(component), + }); +} diff --git a/pages/dev/column-editor/+guard.ts b/pages/dev/column-editor/+guard.ts new file mode 100644 index 00000000..6bd9cc8e --- /dev/null +++ b/pages/dev/column-editor/+guard.ts @@ -0,0 +1,10 @@ +import { render } from "vike/abort"; + +// This guard() hook protects all pages /pages/admin/**/+Page.js +// https://vike.dev/guard + +export async function guard(pageContext) { + if (pageContext.user?.role != "web_admin") { + throw render(401, "You aren't allowed to access this page."); + } +} diff --git a/pages/dev/me/+Page.client.ts b/pages/dev/me/+Page.client.ts new file mode 100644 index 00000000..0f44ef7d --- /dev/null +++ b/pages/dev/me/+Page.client.ts @@ -0,0 +1,7 @@ +import h from "@macrostrat/hyper"; +import { DocumentationPage } from "~/layouts"; +import { AuthStatus } from "@macrostrat/auth-components"; + +export function Page() { + return h(DocumentationPage, { title: "Login" }, [h(AuthStatus)]); +} diff --git a/pages/integrations/xdd/types/+Page.client.ts b/pages/integrations/xdd/types/+Page.client.ts index d32ff03b..dd3def67 100644 --- a/pages/integrations/xdd/types/+Page.client.ts +++ b/pages/integrations/xdd/types/+Page.client.ts @@ -9,7 +9,6 @@ import { ColorPicker, } from "@macrostrat/data-sheet2"; import { asChromaColor } from "@macrostrat/color-utils"; -import { LoginButton } from "#/maps/ingestion/components/navbar"; import { AuthStatus } from "@macrostrat/auth-components"; const colorField = { diff --git a/server/vike-handler.ts b/server/vike-handler.ts index bf50f31a..12d71d95 100644 --- a/server/vike-handler.ts +++ b/server/vike-handler.ts @@ -35,17 +35,16 @@ export async function vikeHandler< async function getUserFromCookie(cookies: Record) { // Pull out the authorization cookie and decrypt it - let user: any = undefined; + let user: any = null; try { const authHeader = cookies?.["access_token"]; const secret = new TextEncoder().encode(process.env.SECRET_KEY); const jwt = authHeader.substring(7, authHeader.length); - // We probably don't need to verify the JWT on each request. - // OR we can pass the user obju - user = (await jose.jwtVerify(jwt, secret)).payload; + let res = await jose.jwtVerify(jwt, secret); + user = res.payload; console.log("User", user); } catch (e) { - // I don't care if it fails, it just means the user isn't logged in + // If it fails, the user isn't logged in. Could also have an expired token... console.log("Anonymous user"); } @@ -67,9 +66,9 @@ function getCookies(request: Request) { function synthesizeConfigFromEnvironment() { /** Creates a mapping of environment variables that start with VITE_, * and returns them as an object. This allows us to pass environment - * variables to the client. + * variables to the client at runtime. * - * TODO: Ideally this would be defined in library code. + * TODO: Ideally this would be defined in a library. * */ const env = {}; for (const key of Object.keys(process.env)) { diff --git a/vite.config.ts b/vite.config.ts index 2ddf4f11..32893bc6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -100,7 +100,12 @@ export default defineConfig({ // If not building for server context }, ssr: { - noExternal: ["labella", "@supabase/postgrest-js"], + // https://vike.dev/broken-npm-package + noExternal: [ + "labella", + "@supabase/postgrest-js", + "@macrostrat/auth-components", + ], }, css: { preprocessorOptions: {