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' }); }