Merge pull request #41 from MaxLeiter/database

server: enable sqlite3 database and document env vars
This commit is contained in:
Max Leiter 2022-03-21 00:30:03 -06:00 committed by GitHub
commit b84d982be2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 16 deletions

View file

@ -21,6 +21,24 @@ 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/`. 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.
- `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 ## 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. 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 +52,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)) - [ ] SSO via HTTP header (Issue: [#11](https://github.com/MaxLeiter/Drift/issues/11))
- [x] downloading files (individually and entire posts) - [x] downloading files (individually and entire posts)
- [ ] password protected 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 - [ ] non-node backend
- [ ] administrator account / settings - [ ] administrator account / settings
- [ ] docker-compose (PR: [#13](https://github.com/MaxLeiter/Drift/pull/13)) - [ ] docker-compose (PR: [#13](https://github.com/MaxLeiter/Drift/pull/13))

View file

@ -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 { Button, Input, Text, Note } from '@geist-ui/core'
import styles from './auth.module.css' import styles from './auth.module.css'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -13,10 +13,29 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [serverPassword, setServerPassword] = useState('');
const [errorMsg, setErrorMsg] = useState(''); const [errorMsg, setErrorMsg] = useState('');
const [requiresServerPassword, setRequiresServerPassword] = useState(false);
const signingIn = page === 'signin' 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) => { const handleJson = (json: any) => {
Cookies.set('drift-token', json.token); Cookies.set('drift-token', json.token);
Cookies.set('drift-userid', json.userId); Cookies.set('drift-userid', json.userId);
@ -24,10 +43,10 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
router.push('/') router.push('/')
} }
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault() 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(''); else setErrorMsg('');
const reqOpts = { const reqOpts = {
@ -35,14 +54,13 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ username, password }) body: JSON.stringify({ username, password, serverPassword })
} }
try { try {
const signUrl = signingIn ? '/server-api/auth/signin' : '/server-api/auth/signup'; const signUrl = signingIn ? '/server-api/auth/signin' : '/server-api/auth/signup';
const resp = await fetch(signUrl, reqOpts); const resp = await fetch(signUrl, reqOpts);
const json = await resp.json(); const json = await resp.json();
console.log(json)
if (!resp.ok) throw new Error(json.error.message); if (!resp.ok) throw new Error(json.error.message);
handleJson(json) handleJson(json)
@ -78,6 +96,16 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
required required
scale={4 / 3} scale={4 / 3}
/> />
{requiresServerPassword && <Input
htmlType='password'
id="server-password"
value={serverPassword}
onChange={(event) => setServerPassword(event.target.value)}
placeholder="Server Password"
required
scale={4 / 3}
/>}
<Button type="success" htmlType="submit">{signingIn ? 'Sign In' : 'Sign Up'}</Button> <Button type="success" htmlType="submit">{signingIn ? 'Sign In' : 'Sign Up'}</Button>
</div> </div>
<div className={styles.formContentSpace}> <div className={styles.formContentSpace}>

1
server/.gitignore vendored
View file

@ -1,3 +1,4 @@
.env .env
node_modules/ node_modules/
dist/ dist/
drift.sqlite

View file

@ -2,7 +2,8 @@ import {Sequelize} from 'sequelize-typescript';
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({
dialect: 'sqlite', dialect: 'sqlite',
database: 'movies', database: 'drift',
storage: ':memory:', storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + './../drift.sqlite',
models: [__dirname + '/models'] models: [__dirname + '/models'],
host: 'localhost',
}); });

View file

@ -7,17 +7,26 @@ import jwt from '../../lib/middleware/jwt'
const NO_EMPTY_SPACE_REGEX = /^\S*$/ 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() 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) { if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) {
throw new Error("Authentication data does not fulfill requirements") 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) => { auth.post('/signup', async (req, res, next) => {
try { try {
validateAuthPayload(req.body.username, req.body.password) validateAuthPayload(req.body.username, req.body.password, req.body.serverPassword)
const username = req.body.username.toLowerCase(); 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) { function generateAccessToken(id: string) {
return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' }); return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' });
} }

View file

@ -4,8 +4,7 @@ import config from '../lib/config';
import { sequelize } from '../lib/sequelize'; import { sequelize } from '../lib/sequelize';
(async () => { (async () => {
await sequelize.sync({ force: true }); await sequelize.sync();
createServer(app) createServer(app)
.listen( .listen(
config.port, config.port,