From b5fcab818a2a9ce27d1e183a1f771cca748092fa Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 30 Nov 2023 12:10:29 -0600 Subject: [PATCH] Add ability to lock down pages --- package.json | 4 +++- server/index.js | 25 ++++++++++++++++++++- src/pages/dev/security/index.page.route.ts | 20 +++++++++++++++++ src/pages/dev/security/index.page.ts | 9 ++++++++ yarn.lock | 26 ++++++++++++++++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/pages/dev/security/index.page.route.ts create mode 100644 src/pages/dev/security/index.page.ts diff --git a/package.json b/package.json index c5d626bb..b7c3e935 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "bootstrap": "yarn", "dev": "yarn run server:dev", "build": "vite build", - "server": "node ./server/index.js", + "server": "node --env-file=.env ./server/index.js", "server:dev": "yarn run server", "server:prod": "cross-env NODE_ENV=production npm run server" }, @@ -90,6 +90,7 @@ "chroma-js": "^2.4.2", "classnames": "^2.2.6", "compression": "^1.7.4", + "cookie-parser": "^1.4.6", "cross-env": "^7.0.3", "d3-array": "^3.1.1", "d3-axis": "^3.0.0", @@ -102,6 +103,7 @@ "express": "^4.18.2", "history": "^5.3.0", "immutability-helper": "^3.1.1", + "jose": "^5.1.2", "mapbox-gl": "^2.15.0", "new-github-issue-url": "^1.0.0", "pbf": "^3.2.1", diff --git a/server/index.js b/server/index.js index 0a9e24ce..6fb8c4ae 100644 --- a/server/index.js +++ b/server/index.js @@ -8,6 +8,11 @@ import express from 'express' import compression from 'compression' import { renderPage } from 'vite-plugin-ssr/server' import { root } from './root.js' + +// Auth imports +import cookieParser from 'cookie-parser' +import * as jose from 'jose' + const isProduction = process.env.NODE_ENV === 'production' startServer() @@ -16,6 +21,7 @@ async function startServer() { const app = express() app.use(compression()) + app.use(cookieParser()) // Vite integration if (isProduction) { @@ -45,8 +51,25 @@ async function startServer() { // catch-all middleware superseding any middleware placed after it). app.get('*', async (req, res, next) => { + // Pull out the authorization cookie and decrypt it + let user = undefined + + try { + const authHeader = req.cookies?.Authorization + const secret = new TextEncoder().encode( + process.env.SECRET_KEY + ); + const jwt = authHeader.substring(7, authHeader.length) + user = (await jose.jwtVerify(jwt, secret)).payload + + + } catch (e) { + // I don't care if it fails, it just means the user isn't logged in + } + const pageContextInit = { - urlOriginal: req.originalUrl + urlOriginal: req.originalUrl, + user: user } const pageContext = await renderPage(pageContextInit) diff --git a/src/pages/dev/security/index.page.route.ts b/src/pages/dev/security/index.page.route.ts new file mode 100644 index 00000000..7ffb09e9 --- /dev/null +++ b/src/pages/dev/security/index.page.route.ts @@ -0,0 +1,20 @@ +import { render, redirect } from 'vite-plugin-ssr/abort' + +export const guard = (pageContext) => { + const { user } = pageContext + + console.log("User: ", user) + + if (user === undefined) { + // Render the login page while preserving the URL. (This is novel technique + // which we explain down below.) + throw redirect(`${import.meta.env.VITE_MACROSTRAT_INGEST_API}/security/login?return_url=${pageContext.url}`) + /* The more traditional way, redirect the user: + throw redirect('/login') + */ + } + if (!user.groups.includes("admin")) { + // Render the error page and show message to the user + throw render(403, 'Only admins are allowed to access this page.') + } +} \ No newline at end of file diff --git a/src/pages/dev/security/index.page.ts b/src/pages/dev/security/index.page.ts new file mode 100644 index 00000000..7dd66255 --- /dev/null +++ b/src/pages/dev/security/index.page.ts @@ -0,0 +1,9 @@ +import {default as h} from "@macrostrat/hyper"; + +export function Page() { + + return h("div", [ + h("h1", "Secure Page") + ]); +} + diff --git a/yarn.lock b/yarn.lock index 8d8434d4..97dc8af7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3888,6 +3888,7 @@ __metadata: chroma-js: ^2.4.2 classnames: ^2.2.6 compression: ^1.7.4 + cookie-parser: ^1.4.6 cross-env: ^7.0.3 d3-array: ^3.1.1 d3-axis: ^3.0.0 @@ -3900,6 +3901,7 @@ __metadata: express: ^4.18.2 history: ^5.3.0 immutability-helper: ^3.1.1 + jose: ^5.1.2 mapbox-gl: ^2.15.0 new-github-issue-url: ^1.0.0 pbf: ^3.2.1 @@ -10918,6 +10920,16 @@ __metadata: languageName: node linkType: hard +"cookie-parser@npm:^1.4.6": + version: 1.4.6 + resolution: "cookie-parser@npm:1.4.6" + dependencies: + cookie: 0.4.1 + cookie-signature: 1.0.6 + checksum: 1e5a63aa82e8eb4e02d2977c6902983dee87b02e87ec5ec43ac3cb1e72da354003716570cd5190c0ad9e8a454c9d3237f4ad6e2f16d0902205a96a1c72b77ba5 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -10925,6 +10937,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.4.1": + version: 0.4.1 + resolution: "cookie@npm:0.4.1" + checksum: bd7c47f5d94ab70ccdfe8210cde7d725880d2fcda06d8e375afbdd82de0c8d3b73541996e9ce57d35f67f672c4ee6d60208adec06b3c5fc94cebb85196084cf8 + languageName: node + linkType: hard + "cookie@npm:0.5.0": version: 0.5.0 resolution: "cookie@npm:0.5.0" @@ -18335,6 +18354,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^5.1.2": + version: 5.1.2 + resolution: "jose@npm:5.1.2" + checksum: 035aff9c3413c2dbcb4fe240f14249e59c91225575063c1e27a0944e6b78a24b20b61f3b687ccff2012ff430b335c002f4af7e3599e284475b451752866041a0 + languageName: node + linkType: hard + "jpeg-js@npm:^0.4.1": version: 0.4.4 resolution: "jpeg-js@npm:0.4.4"