From e2c5e2dac9be34b197a219356dae0e3af4d70133 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 20 Mar 2022 23:09:38 -0700 Subject: [PATCH 1/2] server: enable sqlite3 database and document env vars --- README.md | 19 ++++++++++++++++++- server/.gitignore | 3 ++- server/lib/sequelize.ts | 9 +++++---- server/src/server.ts | 3 +-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c28b727b..3d7a4438 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ You can run `yarn dev` in either / both folders to start the server and client w If you're deploying the front-end to something like Vercel, you'll need to set the root folder to `client/`. +### Environment Variables + +You can change these to your liking. + +`client/.env`: + +- `API_URL`: defaults to localhost:3001, but allows you to host the front-end separately from the backend on a service like Vercel or Netlify +- `WELCOME_CONTENT`: a markdown string (with \n newlines) that's rendered on the home page +- `WELCOME_TITLE`: the file title for the post on the homepage. + +`server/.env`: + +- `PORT`: the default port to start the server on (3000 by default) +- `ENV`: can be `production` or `debug`, toggles logging +- `JWT_SECRET`: a secure token for JWT tokens. You can generate one [here](https://www.grc.com/passwords.htm). +- `MEMORY_DB`: if "true", a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. + ## Current status Drift is a major work in progress. Below is a (rough) list of completed and envisioned features. If you want to help address any of them, please let me know regardless of your experience and I'll be happy to assist. @@ -34,7 +51,7 @@ Drift is a major work in progress. Below is a (rough) list of completed and envi - [ ] SSO via HTTP header (Issue: [#11](https://github.com/MaxLeiter/Drift/issues/11)) - [x] downloading files (individually and entire posts) - [ ] password protected posts -- [ ] sqlite database (should be very easy to set-up; the ORM is just currently set to memory for ease of development) +- [x] sqlite database - [ ] non-node backend - [ ] administrator account / settings - [ ] docker-compose (PR: [#13](https://github.com/MaxLeiter/Drift/pull/13)) diff --git a/server/.gitignore b/server/.gitignore index cfbfa63b..2a5caafa 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,3 +1,4 @@ .env node_modules/ -dist/ \ No newline at end of file +dist/ +drift.sqlite \ No newline at end of file diff --git a/server/lib/sequelize.ts b/server/lib/sequelize.ts index 7d53dd05..f364c3bf 100644 --- a/server/lib/sequelize.ts +++ b/server/lib/sequelize.ts @@ -1,8 +1,9 @@ -import {Sequelize} from 'sequelize-typescript'; +import { Sequelize } from 'sequelize-typescript'; export const sequelize = new Sequelize({ dialect: 'sqlite', - database: 'movies', - storage: ':memory:', - models: [__dirname + '/models'] + database: 'drift', + storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + './../drift.sqlite', + models: [__dirname + '/models'], + host: 'localhost', }); diff --git a/server/src/server.ts b/server/src/server.ts index 503dcdba..2e96ea8d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,8 +4,7 @@ import config from '../lib/config'; import { sequelize } from '../lib/sequelize'; (async () => { - await sequelize.sync({ force: true }); - + await sequelize.sync(); createServer(app) .listen( config.port, From c4cd55f4e6d247dff96e0c86a50d3898c89ace3e Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 20 Mar 2022 23:27:09 -0700 Subject: [PATCH 2/2] server/client: add registration password and env vars --- README.md | 3 ++- client/components/auth/index.tsx | 40 +++++++++++++++++++++++++++----- server/src/routes/auth.ts | 21 +++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d7a4438..e5b42585 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ You can change these to your liking. - `PORT`: the default port to start the server on (3000 by default) - `ENV`: can be `production` or `debug`, toggles logging - `JWT_SECRET`: a secure token for JWT tokens. You can generate one [here](https://www.grc.com/passwords.htm). -- `MEMORY_DB`: if "true", a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. +- `MEMORY_DB`: if `true`, a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. +- `REGISTRATION_PASSWORD`: if MEMORY_DB is not `true`, the user will be required to provide this password to sign-up, in addition to their username and account password. If it's not set, no password will be required. ## Current status diff --git a/client/components/auth/index.tsx b/client/components/auth/index.tsx index 1db9c549..c43111d8 100644 --- a/client/components/auth/index.tsx +++ b/client/components/auth/index.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useState } from 'react' +import { FormEvent, useEffect, useState } from 'react' import { Button, Input, Text, Note } from '@geist-ui/core' import styles from './auth.module.css' import { useRouter } from 'next/router' @@ -13,10 +13,29 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); + const [serverPassword, setServerPassword] = useState(''); const [errorMsg, setErrorMsg] = useState(''); - + const [requiresServerPassword, setRequiresServerPassword] = useState(false); const signingIn = page === 'signin' + useEffect(() => { + async function fetchRequiresPass() { + if (!signingIn) { + const resp = await fetch("/server-api/auth/requires-passcode", { + method: "GET", + }) + if (resp.ok) { + const res = await resp.json() + setRequiresServerPassword(res) + } else { + setErrorMsg("Something went wrong.") + } + } + } + fetchRequiresPass() + }, [page, signingIn]) + + const handleJson = (json: any) => { Cookies.set('drift-token', json.token); Cookies.set('drift-userid', json.userId); @@ -24,10 +43,10 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { router.push('/') } - const handleSubmit = async (e: FormEvent) => { e.preventDefault() - if (page === "signup" && (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)) return setErrorMsg(ERROR_MESSAGE) + if (!signingIn && (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)) return setErrorMsg(ERROR_MESSAGE) + if (!signingIn && requiresServerPassword && !NO_EMPTY_SPACE_REGEX.test(serverPassword)) return setErrorMsg(ERROR_MESSAGE) else setErrorMsg(''); const reqOpts = { @@ -35,14 +54,13 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }) + body: JSON.stringify({ username, password, serverPassword }) } try { const signUrl = signingIn ? '/server-api/auth/signin' : '/server-api/auth/signup'; const resp = await fetch(signUrl, reqOpts); const json = await resp.json(); - console.log(json) if (!resp.ok) throw new Error(json.error.message); handleJson(json) @@ -78,6 +96,16 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { required scale={4 / 3} /> + {requiresServerPassword && setServerPassword(event.target.value)} + placeholder="Server Password" + required + scale={4 / 3} + />} +
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 7a3aa7e7..049445c0 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -7,17 +7,26 @@ import jwt from '../../lib/middleware/jwt' const NO_EMPTY_SPACE_REGEX = /^\S*$/ +export const requiresServerPassword = (process.env.MEMORY_DB || process.env.ENV === 'production') && !!process.env.REGISTRATION_PASSWORD +console.log(`Registration password required: ${requiresServerPassword}`) + export const auth = Router() -const validateAuthPayload = (username: string, password: string): void => { +const validateAuthPayload = (username: string, password: string, serverPassword?: string): void => { if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) { throw new Error("Authentication data does not fulfill requirements") } + + if (requiresServerPassword) { + if (!serverPassword || process.env.REGISTRATION_PASSWORD !== serverPassword) { + throw new Error("Server password is incorrect. Please contact the server administrator.") + } + } } auth.post('/signup', async (req, res, next) => { try { - validateAuthPayload(req.body.username, req.body.password) + validateAuthPayload(req.body.username, req.body.password, req.body.serverPassword) const username = req.body.username.toLowerCase(); @@ -69,6 +78,14 @@ auth.post('/signin', async (req, res, next) => { } }); +auth.get('/requires-passcode', async (req, res, next) => { + if (requiresServerPassword) { + res.status(200).json({ requiresPasscode: true }); + } else { + res.status(200).json({ requiresPasscode: false }); + } +}) + function generateAccessToken(id: string) { return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' }); }