Compare commits
15 Commits
e4c038c940
...
2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 284615573f | |||
| ba96584300 | |||
| 832ff99a7b | |||
| 1940d67c37 | |||
| 4153fcd8d5 | |||
| 163fccb9b2 | |||
| cd72390f42 | |||
| b923785c9f | |||
| 08f8cc7fc3 | |||
| 273314739f | |||
| ba6d8eeffc | |||
| 4ccdbfc8a4 | |||
| 34696a1fc8 | |||
| a197d9bd3b | |||
| 91cdc9e08a |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -138,3 +138,9 @@ dist
|
||||
# Vite logs files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
|
||||
# user files
|
||||
|
||||
/responses
|
||||
/data
|
||||
|
||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -11,6 +11,8 @@ COPY . .
|
||||
RUN npm prune --production
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
RUN mkdir -p /data
|
||||
RUN chown node /data
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/node_modules node_modules/
|
||||
COPY --from=builder /app/index.js .
|
||||
@@ -18,4 +20,5 @@ COPY --from=builder /app/src src/
|
||||
USER node
|
||||
EXPOSE 12345
|
||||
ENV NODE_ENV=production
|
||||
ENV JEOPARDY_CDN_DATA_PATH=/data
|
||||
CMD [ "node", "."]
|
||||
|
||||
@@ -25,7 +25,7 @@ Ansonsten kann man auch mit `npm run dev` entwickeln.
|
||||
cd /opt/jeopardy/Jeopardy-Server
|
||||
git fetch --tags
|
||||
git checkout <versionsnummer>
|
||||
docker build -t jeopardy .
|
||||
docker build -t jeopardyserver .
|
||||
docker tag jeopardyserver:latest jeopardyserver:<versionsnummer>
|
||||
cd ..
|
||||
docker compose up -d
|
||||
|
||||
30
index.js
30
index.js
@@ -1,22 +1,21 @@
|
||||
import dotenv from "dotenv";
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import express from "express";
|
||||
import expressWs from "express-ws";
|
||||
import morgan from "morgan";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { initWebsocket } from "./src/websocket.js";
|
||||
import { initAuth } from "./src/auth.js";
|
||||
import { close as closeDbConnection, initDbConnection, db } from "./src/db.js";
|
||||
import express from 'express';
|
||||
import expressWs from 'express-ws';
|
||||
import morgan from 'morgan';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
import { initWebsocket } from './src/websocket.js';
|
||||
import { initAuth } from './src/auth.js';
|
||||
import { initDbConnection, db } from './src/db.js';
|
||||
import { initUsers } from './src/user.js';
|
||||
import { initCdn } from './src/cdn.js';
|
||||
import { initGames } from './src/games.js';
|
||||
const app = express();
|
||||
const appWs = expressWs(app);
|
||||
const port = 12345;
|
||||
|
||||
process.on('exit', function() {
|
||||
console.log('Shutting down...');
|
||||
console.log('Closing db connection...');
|
||||
closeDbConnection();
|
||||
});
|
||||
|
||||
app.use(cors({ credentials: true, origin: process.env.JEOPARDY_URL }));
|
||||
app.use(morgan(process.env.production ? 'common' : 'dev'));
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
@@ -24,7 +23,10 @@ app.use(cookieParser());
|
||||
await initDbConnection();
|
||||
|
||||
initAuth(app, db);
|
||||
initUsers(app, db);
|
||||
initWebsocket(app);
|
||||
initCdn(app, db);
|
||||
initGames(app, db);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
|
||||
219
package-lock.json
generated
219
package-lock.json
generated
@@ -1,25 +1,28 @@
|
||||
{
|
||||
"name": "jeopardyserver",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jeopardyserver",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"express-ws": "^5.0.2",
|
||||
"mongodb": "^6.20.0",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^2.0.2",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.6.0"
|
||||
"@types/node": "^24.6.0",
|
||||
"prettier": "^3.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
@@ -155,6 +158,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
@@ -202,6 +211,23 @@
|
||||
"node": ">=16.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -240,6 +266,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
@@ -298,6 +339,19 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@@ -720,6 +774,27 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
|
||||
@@ -825,6 +900,67 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
|
||||
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"mkdirp": "^0.5.6",
|
||||
"object-assign": "^4.1.1",
|
||||
"type-is": "^1.6.18",
|
||||
"xtend": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
@@ -834,6 +970,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@@ -895,6 +1040,22 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -972,6 +1133,20 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
@@ -1147,6 +1322,23 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@@ -1182,6 +1374,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
|
||||
@@ -1197,6 +1395,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@@ -1254,6 +1458,15 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jeopardyserver",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
@@ -13,14 +13,17 @@
|
||||
"dependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"express-ws": "^5.0.2",
|
||||
"mongodb": "^6.20.0",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^2.0.2",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.6.0"
|
||||
"@types/node": "^24.6.0",
|
||||
"prettier": "^3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,9 @@ content-type: application/json
|
||||
|
||||
{
|
||||
"username": "jonas",
|
||||
"password": "kappa"
|
||||
"password": "jonas"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET {{url}}/auth HTTP/1.1
|
||||
55
requests/user.http
Normal file
55
requests/user.http
Normal file
@@ -0,0 +1,55 @@
|
||||
@url = http://{{host}}:{{port}}
|
||||
|
||||
PUT {{url}}/admin/user HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "Paula"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET {{url}}/admin/user/list HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
POST {{url}}/admin/user/resetpw HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userid": "68e1058faf78b3aabbdfe8dc"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET {{url}}/admin/roles HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
POST {{url}}/admin/user/changerole HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userid": "68e0efc6e4ac740114d8fc9d",
|
||||
"role": "default"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST {{url}}/user/changepw HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"old": "DkgnWspm4To2ww==",
|
||||
"new": "Kolata"
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
|
||||
DELETE {{url}}/admin/user HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"userid": "68e0f66a7b5795e3704501cf"
|
||||
}
|
||||
120
src/auth.js
120
src/auth.js
@@ -1,24 +1,37 @@
|
||||
import { createHash, pbkdf2Sync, randomBytes } from "node:crypto";
|
||||
import { createUser, generateHash, updateSessionToken } from './userHelper.js';
|
||||
|
||||
let db;
|
||||
let users;
|
||||
|
||||
export function initAuth(app, db) {
|
||||
app.use(checkSessionToken);
|
||||
app.use('/admin', checkAuthorization('admin'));
|
||||
users = db.collection('users');
|
||||
app.get('/auth', getUserInfo);
|
||||
app.post('/auth/login', loginUser);
|
||||
}
|
||||
|
||||
async function checkSessionToken(req, res, next) {
|
||||
async function getUserInfo(req, res) {
|
||||
// const sessiontoken = await updateSessionToken(users, req.user._id);
|
||||
|
||||
if (req.path.startsWith("/auth/")) {
|
||||
// setTokenCookie(res, sessiontoken);
|
||||
|
||||
res.status(200).send({
|
||||
username: req.user.username,
|
||||
role: req.user.role,
|
||||
_id: req.user._id,
|
||||
});
|
||||
}
|
||||
|
||||
async function checkSessionToken(req, res, next) {
|
||||
if (req.path.startsWith('/auth/')) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const token = req.cookies.jeopardytoken;
|
||||
|
||||
let user = await users.findOne({sessiontoken: token});
|
||||
let user = await users.findOne({ sessiontoken: token });
|
||||
|
||||
if (user === null) {
|
||||
res.sendStatus(401);
|
||||
@@ -27,80 +40,93 @@ async function checkSessionToken(req, res, next) {
|
||||
|
||||
req.user = {
|
||||
role: user.role,
|
||||
username: user.username
|
||||
}
|
||||
username: user.username,
|
||||
_id: user._id,
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function checkAuthorization(role) {
|
||||
return (req, res, next) => {
|
||||
if (req.user === undefined) {
|
||||
res.status(403).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.user.role === role) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function loginUser(req, res) {
|
||||
const username = req.body.username;
|
||||
const password = req.body.password;
|
||||
|
||||
let userCount = await users.estimatedDocumentCount();
|
||||
let sessiontoken = null;
|
||||
let userobj = null;
|
||||
if (userCount <= 0) {
|
||||
// create first user
|
||||
sessiontoken = await createUser(username, password, 'admin');
|
||||
userobj = await createUser(users, username, password, 'admin', true);
|
||||
} else {
|
||||
// authenticate user
|
||||
sessiontoken = await authenticateUser(username, password);
|
||||
userobj = await authenticateUser(username, password);
|
||||
}
|
||||
|
||||
if (sessiontoken !== null) {
|
||||
const expires = new Date();
|
||||
expires.setDate(expires.getDate() + 1);
|
||||
if (userobj !== null) {
|
||||
setTokenCookie(res, userobj.sessiontoken);
|
||||
|
||||
res.cookie('jeopardytoken', sessiontoken, {
|
||||
maxAge: 1e3 * 60 * 60 * 24
|
||||
})
|
||||
|
||||
res.status(200).send(username);
|
||||
res.status(200).send({
|
||||
username: userobj.username,
|
||||
role: userobj.role,
|
||||
_id: userobj._id,
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
}
|
||||
|
||||
async function createUser(username, password, role) {
|
||||
const salt = randomBytes(128).toString('base64');
|
||||
const iterations = Math.floor(Math.random() * 5000) + 5000;
|
||||
const hash = generateHash(password, salt, iterations);
|
||||
|
||||
const sessiontoken = generateSessionToken();
|
||||
|
||||
await users.insertOne({
|
||||
export async function authenticateUser(
|
||||
username,
|
||||
role,
|
||||
salt,
|
||||
iterations,
|
||||
hash,
|
||||
sessiontoken
|
||||
});
|
||||
|
||||
return sessiontoken;
|
||||
}
|
||||
|
||||
async function authenticateUser(username, password) {
|
||||
let foundUser = await users.findOne({username});
|
||||
password,
|
||||
updateSession = true,
|
||||
) {
|
||||
let foundUser = await users.findOne({ username });
|
||||
if (foundUser === null) return null;
|
||||
|
||||
const hash = generateHash(password, foundUser.salt, foundUser.iterations);
|
||||
|
||||
if (hash === foundUser.hash) {
|
||||
const sessiontoken = generateSessionToken();
|
||||
await users.updateOne({_id: foundUser._id}, {$set: {
|
||||
sessiontoken
|
||||
}});
|
||||
return sessiontoken;
|
||||
if (updateSession) {
|
||||
let sessiontoken = await updateSessionToken(users, foundUser._id);
|
||||
return {
|
||||
sessiontoken,
|
||||
username,
|
||||
role: foundUser.role,
|
||||
_id: foundUser._id,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sessiontoken: foundUser.sessiontoken,
|
||||
username,
|
||||
role: foundUser.role,
|
||||
_id: foundUser._id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function generateSessionToken() {
|
||||
return randomBytes(128).toString('base64');
|
||||
}
|
||||
function setTokenCookie(res, sessiontoken) {
|
||||
const expires = new Date();
|
||||
expires.setDate(expires.getDate() + 1);
|
||||
|
||||
function generateHash(password, salt, iterations) {
|
||||
return pbkdf2Sync(password, salt, iterations, 128, 'sha512').toString('hex');
|
||||
res.cookie('jeopardytoken', sessiontoken, {
|
||||
maxAge: 1e3 * 60 * 60 * 24 * 7,
|
||||
path: '/',
|
||||
});
|
||||
}
|
||||
|
||||
328
src/cdn.js
Normal file
328
src/cdn.js
Normal file
@@ -0,0 +1,328 @@
|
||||
import { rmSync } from 'fs';
|
||||
import { copyFile, mkdir, readdir, rm } from 'fs/promises';
|
||||
import { Collection, Db, ObjectId } from 'mongodb';
|
||||
import multer from 'multer';
|
||||
|
||||
const dataPath = process.env.JEOPARDY_CDN_DATA_PATH;
|
||||
const upload = multer({ dest: dataPath });
|
||||
|
||||
/**
|
||||
* @type {Collection}
|
||||
*/
|
||||
let ressources;
|
||||
|
||||
function buildPath(userid, path) {
|
||||
return `${dataPath}/${userid}${path.length === '/' ? '' : path}`;
|
||||
}
|
||||
|
||||
function ressourcePath(res) {
|
||||
return `${res.fullpath}/${res.filename}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} app
|
||||
* @param {Db} db
|
||||
*/
|
||||
export function initCdn(app, db) {
|
||||
ressources = db.collection('ressources');
|
||||
app.post('/upload', upload.single('file'), uploadFile);
|
||||
app.get('/cdn/:userid/:resid', fetchFile);
|
||||
app.put('/cdn/:userid/:resid', renameFile);
|
||||
app.delete('/cdn/:userid/:resid', deleteFile);
|
||||
app.get('/ressource', fetchRessource);
|
||||
app.post('/directory', fetchDirectory);
|
||||
app.put('/directory', addDirectory);
|
||||
app.delete('/directory', deleteDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
function uploadFile(req, res, next) {
|
||||
console.log(req.file, req.body);
|
||||
|
||||
/**
|
||||
* @type {string | undefined}
|
||||
*/
|
||||
const path = req.body.path;
|
||||
|
||||
if (path !== undefined && path.startsWith('/') && !path.includes('.')) {
|
||||
let destinationPath = buildPath(req.user._id, req.body.path);
|
||||
mkdir(destinationPath, {
|
||||
recursive: true,
|
||||
})
|
||||
.then(() => {
|
||||
return copyFile(
|
||||
req.file.path,
|
||||
`${destinationPath}/${req.file.filename}`,
|
||||
);
|
||||
})
|
||||
.then(async () => {
|
||||
rmSync(req.file.path);
|
||||
|
||||
await ressources.insertOne({
|
||||
fullpath: destinationPath,
|
||||
path: path,
|
||||
user: req.user._id,
|
||||
mimetype: req.file.mimetype,
|
||||
name: req.file.originalname,
|
||||
filename: req.file.filename,
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
rmSync(req.file.path);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
} else {
|
||||
rmSync(req.file.path);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchRessource(req, res) {
|
||||
if (req.query.id === undefined || req.query.id.length <= 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = new ObjectId(req.query.id);
|
||||
|
||||
ressources
|
||||
.findOne({
|
||||
_id: id,
|
||||
user: req.user._id,
|
||||
})
|
||||
.then((ressource) => {
|
||||
if (ressource) {
|
||||
res.status(200).send(ressource);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchFile(req, res) {
|
||||
let ressource = await ressources.findOne({
|
||||
user: new ObjectId(req.params.userid),
|
||||
_id: new ObjectId(req.params.resid),
|
||||
});
|
||||
|
||||
if (ressource) {
|
||||
res.sendFile(ressourcePath(ressource));
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function renameFile(req, res) {
|
||||
if (req.user._id.toString() !== req.params.userid) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
req.body === undefined ||
|
||||
req.body.name === undefined ||
|
||||
req.body.name.length <= 0 ||
|
||||
/\.{2}|\/|\\/.test(req.body.name)
|
||||
) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const name = req.body.name;
|
||||
|
||||
let ressource = await ressources.findOne({
|
||||
user: new ObjectId(req.params.userid),
|
||||
filename: req.params.resid,
|
||||
});
|
||||
|
||||
if (!ressource) {
|
||||
console.log('Ressource could not be found');
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
ressources
|
||||
.updateOne(
|
||||
{
|
||||
_id: ressource._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function deleteFile(req, res) {
|
||||
if (req.user._id.toString() !== req.params.userid) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
let ressource = await ressources.findOne({
|
||||
user: new ObjectId(req.params.userid),
|
||||
filename: req.params.resid,
|
||||
});
|
||||
|
||||
if (!ressource) {
|
||||
console.log('Ressource could not be found');
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
rm(ressourcePath(ressource))
|
||||
.then(() => {
|
||||
return ressources.deleteOne({ _id: ressource._id });
|
||||
})
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchDirectory(req, res) {
|
||||
if (!req.body) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
const path = req.body.path;
|
||||
if (!path) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = ressources.find({
|
||||
path,
|
||||
});
|
||||
|
||||
readdir(buildPath(req.user._id, path), {
|
||||
withFileTypes: true,
|
||||
})
|
||||
.then(async (value) => {
|
||||
let directories = value
|
||||
.filter((dir) => dir.isDirectory())
|
||||
.map((dir) => {
|
||||
return {
|
||||
name: dir.name,
|
||||
isDir: true,
|
||||
};
|
||||
});
|
||||
|
||||
res.status(200).send([...directories, ...(await files.toArray())]);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
console.error(err);
|
||||
res.status(200).send(await files.toArray());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function addDirectory(req, res) {
|
||||
if (!req.body) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
const name = req.body.name;
|
||||
const path = req.body.path;
|
||||
if (!name || !path || !/^[a-zA-Z0-9-_]+$/.test(name)) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
mkdir(buildPath(req.user._id, path + '/' + name))
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function deleteDirectory(req, res) {
|
||||
console.log(req.body);
|
||||
if (
|
||||
req.body === undefined ||
|
||||
req.body.path === undefined ||
|
||||
req.body.path.length <= 0
|
||||
) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
const path = req.body.path;
|
||||
|
||||
rm(buildPath(req.user._id, path), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
})
|
||||
.then(() => {
|
||||
return ressources.deleteMany({
|
||||
user: req.user._id,
|
||||
path: {
|
||||
$regex: '^' + path + '($|\\/.*)',
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
13
src/db.js
13
src/db.js
@@ -1,5 +1,8 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
import { MongoClient } from 'mongodb';
|
||||
|
||||
/**
|
||||
* @type {MongoClient}
|
||||
*/
|
||||
let client;
|
||||
|
||||
const dbName = `jeopardy`;
|
||||
@@ -10,12 +13,10 @@ const dbName = `jeopardy`;
|
||||
export let db;
|
||||
|
||||
export async function initDbConnection() {
|
||||
client = new MongoClient(`mongodb://${process.env.JEOPARDYSERVER_MONGO_USERNAME}:${process.env.JEOPARDYSERVER_MONGO_PASSWORD}@${process.env.JEOPARDYSERVER_MONGO_URL}/`);
|
||||
client = new MongoClient(
|
||||
`mongodb://${process.env.JEOPARDYSERVER_MONGO_USERNAME}:${process.env.JEOPARDYSERVER_MONGO_PASSWORD}@${process.env.JEOPARDYSERVER_MONGO_URL}/`,
|
||||
);
|
||||
await client.connect();
|
||||
console.log('Connected successfully to mongodb');
|
||||
db = client.db(dbName);
|
||||
}
|
||||
|
||||
export function close() {
|
||||
client.close();
|
||||
}
|
||||
|
||||
761
src/games.js
Normal file
761
src/games.js
Normal file
@@ -0,0 +1,761 @@
|
||||
import { Collection, Db, ObjectId } from 'mongodb';
|
||||
import { checkNumberProp, checkObjectProp, checkStringProp } from './util.js';
|
||||
|
||||
/**
|
||||
* @type {Collection}
|
||||
*/
|
||||
let cGames;
|
||||
/**
|
||||
* @type {Collection}
|
||||
*/
|
||||
let cWalls;
|
||||
/**
|
||||
* @type {Collection}
|
||||
*/
|
||||
let cCategories;
|
||||
/**
|
||||
* @type {Collection}
|
||||
*/
|
||||
let cQuestions;
|
||||
|
||||
const QuestionType = {
|
||||
SIMPLE: 'SIMPLE',
|
||||
MULTIPLE_CHOICE: 'MULTIPLE_CHOICE',
|
||||
IMAGE: 'IMAGE',
|
||||
IMAGE_MULTIPLE_CHOICE: 'IMAGE_MULTIPLE_CHOICE',
|
||||
AUDIO: 'AUDIO',
|
||||
AUDIO_MULTIPLE_CHOICE: 'AUDIO_MULTIPLE_CHOICE',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} points
|
||||
* @param {string} question
|
||||
* @param {string} answer
|
||||
* @param {ObjectId} owner
|
||||
* @returns
|
||||
*/
|
||||
function createSimpleQuestion(points, question, answer, owner) {
|
||||
return {
|
||||
owner,
|
||||
points,
|
||||
type: QuestionType.SIMPLE,
|
||||
data: createSimpleData(question, answer),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} question
|
||||
* @param {string} answer
|
||||
* @returns
|
||||
*/
|
||||
function createSimpleData(question, answer) {
|
||||
return {
|
||||
question,
|
||||
answer,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ObjectId[]} ids
|
||||
*/
|
||||
function splitQuestionIds(ids) {
|
||||
let res = [];
|
||||
for (let i = 0; i < ids.length; i += 5) {
|
||||
res.push(ids.slice(i, i + 5));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} app
|
||||
* @param {Db} db
|
||||
*/
|
||||
export function initGames(app, db) {
|
||||
cGames = db.collection('games');
|
||||
cWalls = db.collection('walls');
|
||||
cCategories = db.collection('categories');
|
||||
cQuestions = db.collection('questions');
|
||||
app.get('/game', fetchGame);
|
||||
app.post('/game', createGame);
|
||||
app.delete('/game/:gameid', deleteGameRoute);
|
||||
app.get('/games', fetchGames);
|
||||
app.post('/game/rename', renameGame);
|
||||
|
||||
app.get('/wall', fetchWall);
|
||||
app.get('/walls/:gameid', fetchWalls);
|
||||
app.post('/wall', createWall);
|
||||
app.delete('/wall/:wallid', deleteWallRoute);
|
||||
app.post('/wall/rename', renameWall);
|
||||
|
||||
app.get('/category', fetchCategory);
|
||||
app.post('/category/rename', renameCategory);
|
||||
|
||||
app.get('/question', fetchQuestion);
|
||||
app.post('/question', updateQuestion);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function createGame(req, res) {
|
||||
if (!checkStringProp(req.body, 'name')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const name = req.body.name;
|
||||
|
||||
cGames
|
||||
.insertOne({
|
||||
name,
|
||||
walls: [],
|
||||
owner: req.user._id,
|
||||
})
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
function renameGame(req, res) {
|
||||
if (!checkStringProp(req.body, 'name')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
if (!checkStringProp(req.body, 'gameid')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let gameid = req.body.gameid;
|
||||
let _id = new ObjectId(gameid);
|
||||
let name = req.body.name;
|
||||
|
||||
cGames
|
||||
.updateOne(
|
||||
{
|
||||
_id,
|
||||
owner: req.user._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.modifiedCount === 1) res.sendStatus(200);
|
||||
else {
|
||||
console.error(
|
||||
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
||||
);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchGames(req, res) {
|
||||
let list = cGames.find({
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
res.status(200).send(await list.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchGame(req, res) {
|
||||
if (req.query.id === undefined || req.query.id.length <= 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = new ObjectId(req.query.id);
|
||||
|
||||
let game = await cGames.findOne({
|
||||
_id: id,
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
if (game) {
|
||||
res.status(200).send(game);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function deleteGameRoute(req, res) {
|
||||
let game = await cGames.findOne({
|
||||
owner: req.user._id,
|
||||
_id: new ObjectId(req.params.gameid),
|
||||
});
|
||||
|
||||
if (!game) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
deleteGame(game._id)
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchQuestion(req, res) {
|
||||
if (req.query.id === undefined || req.query.id.length <= 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = new ObjectId(req.query.id);
|
||||
|
||||
let question = await cQuestions.findOne({
|
||||
_id: id,
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
if (question) {
|
||||
res.status(200).send(question);
|
||||
} else {
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchCategory(req, res) {
|
||||
if (req.query.id === undefined || req.query.id.length <= 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = new ObjectId(req.query.id);
|
||||
|
||||
let category = await cCategories.findOne({
|
||||
_id: id,
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
if (category) {
|
||||
let questions = await cQuestions
|
||||
.find(
|
||||
{
|
||||
_id: {
|
||||
$in: category.questions,
|
||||
},
|
||||
owner: req.user._id,
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: 1,
|
||||
points: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
.toArray();
|
||||
|
||||
if (questions.length !== 5) {
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
|
||||
category.questions = questions;
|
||||
|
||||
res.status(200).send(category);
|
||||
} else {
|
||||
res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
function renameCategory(req, res) {
|
||||
if (!checkStringProp(req.body, 'name')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
if (!checkStringProp(req.body, 'categoryid')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let categoryid = req.body.categoryid;
|
||||
let _id = new ObjectId(categoryid);
|
||||
let name = req.body.name;
|
||||
|
||||
cCategories
|
||||
.updateOne(
|
||||
{
|
||||
_id,
|
||||
owner: req.user._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.modifiedCount === 1) res.sendStatus(200);
|
||||
else {
|
||||
console.error(
|
||||
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
||||
);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchWall(req, res) {
|
||||
if (req.query.id === undefined || req.query.id.length <= 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = new ObjectId(req.query.id);
|
||||
|
||||
let wall = await cWalls.findOne({
|
||||
_id: id,
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
if (wall) {
|
||||
res.status(200).send(wall);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function fetchWalls(req, res) {
|
||||
let game = await cGames.findOne({
|
||||
owner: req.user._id,
|
||||
_id: new ObjectId(req.params.gameid),
|
||||
});
|
||||
|
||||
if (!game) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
let fetchedWalls = cWalls.find({
|
||||
_id: {
|
||||
$in: game.walls,
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).send(await fetchedWalls.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
function renameWall(req, res) {
|
||||
if (!checkStringProp(req.body, 'name')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
if (!checkStringProp(req.body, 'wallid')) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let wallid = req.body.wallid;
|
||||
let _id = new ObjectId(wallid);
|
||||
let name = req.body.name;
|
||||
|
||||
cWalls
|
||||
.updateOne(
|
||||
{
|
||||
_id,
|
||||
owner: req.user._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.modifiedCount === 1) res.sendStatus(200);
|
||||
else {
|
||||
console.error(
|
||||
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
||||
);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function createWall(req, res) {
|
||||
if (
|
||||
!checkStringProp(req.body, 'gameid') &&
|
||||
!checkStringProp(req.body, 'name')
|
||||
) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
const gameid = req.body.gameid;
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
const wallname = req.body.name;
|
||||
|
||||
let game = await cGames.findOne({
|
||||
owner: req.user._id,
|
||||
_id: new ObjectId(gameid),
|
||||
});
|
||||
|
||||
if (!game) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
let newQuestions = [];
|
||||
|
||||
for (let id = 1; id <= 5; id++) {
|
||||
for (let j = 1; j <= 5; j++) {
|
||||
newQuestions.push(
|
||||
createSimpleQuestion(
|
||||
j * -100,
|
||||
'Frage',
|
||||
'Antwort',
|
||||
req.user._id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cQuestions
|
||||
.insertMany(newQuestions)
|
||||
.then((insertedQuestions) => {
|
||||
let questionsIds = splitQuestionIds(
|
||||
Object.values(insertedQuestions.insertedIds),
|
||||
);
|
||||
let newCategories = [];
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
newCategories.push({
|
||||
name: `Kategorie ${i}`,
|
||||
questions: questionsIds[i - 1],
|
||||
owner: req.user._id,
|
||||
});
|
||||
}
|
||||
|
||||
return cCategories.insertMany(newCategories);
|
||||
})
|
||||
.then((insertedCategories) => {
|
||||
return cWalls.insertOne({
|
||||
name: wallname,
|
||||
categories: Object.values(insertedCategories.insertedIds),
|
||||
owner: req.user._id,
|
||||
});
|
||||
})
|
||||
.then((insertedWall) => {
|
||||
return cGames.updateOne(
|
||||
{
|
||||
_id: game._id,
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
walls: insertedWall.insertedId,
|
||||
},
|
||||
},
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function deleteWallRoute(req, res) {
|
||||
let wall = await cWalls.findOne({
|
||||
_id: new ObjectId(req.params.wallid),
|
||||
owner: req.user._id,
|
||||
});
|
||||
|
||||
if (!wall) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
let game = await cGames.findOne({
|
||||
owner: req.user._id,
|
||||
walls: wall._id,
|
||||
});
|
||||
|
||||
if (!game) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
deleteWall(wall._id)
|
||||
.then(() => {
|
||||
return cGames.updateOne(
|
||||
{
|
||||
_id: game._id,
|
||||
},
|
||||
{
|
||||
$pull: {
|
||||
walls: wall._id,
|
||||
},
|
||||
},
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async function updateQuestion(req, res) {
|
||||
if (
|
||||
!checkStringProp(req.body, '_id') &&
|
||||
!checkStringProp(req.body, 'owner') &&
|
||||
!checkStringProp(req.body, 'type') &&
|
||||
!checkNumberProp(req.body, 'points') &&
|
||||
!checkObjectProp(req.body, 'data')
|
||||
) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.body.owner !== req.user._id.toString()) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
let _id = req.body._id;
|
||||
|
||||
let replacement;
|
||||
|
||||
if (
|
||||
req.body.type === QuestionType.SIMPLE ||
|
||||
req.body.type === QuestionType.MULTIPLE_CHOICE
|
||||
) {
|
||||
replacement = toNormalQuestion(req.body);
|
||||
} else if (
|
||||
req.body.type === QuestionType.IMAGE ||
|
||||
req.body.type === QuestionType.IMAGE_MULTIPLE_CHOICE
|
||||
) {
|
||||
replacement = toImageQuestion(req.body);
|
||||
} else if (
|
||||
req.body.type === QuestionType.AUDIO ||
|
||||
req.body.type === QuestionType.AUDIO_MULTIPLE_CHOICE
|
||||
) {
|
||||
replacement = toAudioQuestion(req.body);
|
||||
}
|
||||
|
||||
cQuestions
|
||||
.replaceOne(
|
||||
{
|
||||
_id: new ObjectId(_id),
|
||||
owner: req.user._id,
|
||||
},
|
||||
replacement,
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.modifiedCount === 1) {
|
||||
res.sendStatus(200);
|
||||
} else res.sendStatus(500);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.sendStatus(500);
|
||||
});
|
||||
}
|
||||
|
||||
function toNormalQuestion(body) {
|
||||
return {
|
||||
...body,
|
||||
_id: new ObjectId(body._id),
|
||||
owner: new ObjectId(body.owner),
|
||||
};
|
||||
}
|
||||
|
||||
function toImageQuestion(body) {
|
||||
return {
|
||||
...body,
|
||||
_id: new ObjectId(body._id),
|
||||
owner: new ObjectId(body.owner),
|
||||
data: {
|
||||
...body.data,
|
||||
image:
|
||||
body.data.image === null ? null : new ObjectId(body.data.image),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function toAudioQuestion(body) {
|
||||
return {
|
||||
...body,
|
||||
_id: new ObjectId(body._id),
|
||||
owner: new ObjectId(body.owner),
|
||||
data: {
|
||||
...body.data,
|
||||
audio:
|
||||
body.data.audio === null ? null : new ObjectId(body.data.audio),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ObjectId} _id
|
||||
*/
|
||||
function deleteGame(_id) {
|
||||
return cGames
|
||||
.findOne({ _id })
|
||||
.then((game) => {
|
||||
let wallDeletions = [];
|
||||
for (const wallId of game.walls) {
|
||||
wallDeletions.push(deleteWall(wallId));
|
||||
}
|
||||
|
||||
return Promise.all(wallDeletions);
|
||||
})
|
||||
.then(() => {
|
||||
return cGames.deleteOne({ _id });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ObjectId} _id
|
||||
*/
|
||||
function deleteWall(_id) {
|
||||
return cWalls
|
||||
.findOne({ _id })
|
||||
.then((wall) => {
|
||||
let categoryDeletions = [];
|
||||
for (const catId of wall.categories) {
|
||||
categoryDeletions.push(deleteCategory(catId));
|
||||
}
|
||||
return Promise.all(categoryDeletions);
|
||||
})
|
||||
.then(() => {
|
||||
return cWalls.deleteOne({ _id: _id });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ObjectId} _id
|
||||
*/
|
||||
function deleteCategory(_id) {
|
||||
return cCategories
|
||||
.findOne({
|
||||
_id,
|
||||
})
|
||||
.then((category) => {
|
||||
return cQuestions.deleteMany({
|
||||
_id: {
|
||||
$in: category.questions,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return cCategories.deleteOne({
|
||||
_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
12
src/roles.js
Normal file
12
src/roles.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export const roles = [
|
||||
"admin",
|
||||
"default"
|
||||
]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} newrole
|
||||
*/
|
||||
export function isValidRole(newrole) {
|
||||
return roles.includes(newrole);
|
||||
}
|
||||
156
src/user.js
Normal file
156
src/user.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Db, ObjectId } from "mongodb";
|
||||
import { createUser as userHelperCreateUser, generateSessionToken, updatePassword, userExists } from "./userHelper.js";
|
||||
import { isValidRole, roles } from "./roles.js";
|
||||
import { authenticateUser } from "./auth.js";
|
||||
|
||||
let db;
|
||||
let users;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} app
|
||||
* @param {Db} db
|
||||
*/
|
||||
export function initUsers(app, db) {
|
||||
users = db.collection('users');
|
||||
app.put('/admin/user', createUser);
|
||||
app.delete('/admin/user', deleteUser);
|
||||
app.get('/admin/user/list', userlist);
|
||||
app.post('/admin/user/resetpw', resetpassword);
|
||||
app.post('/admin/user/changerole', changerole);
|
||||
app.get('/admin/roles', getRoles);
|
||||
app.post('/user/changepw', changePassword);
|
||||
app.post('/user/logout', logoutUser);
|
||||
}
|
||||
|
||||
async function createUser(req, res) {
|
||||
const username = req.body.username;
|
||||
|
||||
if (username.length <= 0) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
// check if user exists
|
||||
let foundUser = await users.findOne({username});
|
||||
|
||||
if (foundUser !== null) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const password = generateSessionToken(10);
|
||||
|
||||
const userobj = await userHelperCreateUser(users, username, password, 'default', false);
|
||||
|
||||
res.status(200).send({
|
||||
username: userobj.username,
|
||||
role: userobj.role,
|
||||
_id: userobj._id,
|
||||
password
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteUser(req, res) {
|
||||
/** @type {string} */
|
||||
const userid = req.body.userid;
|
||||
const _id = new ObjectId(userid);
|
||||
|
||||
if (userid === req.user._id.toString()) {
|
||||
console.log("Cant delete yourself");
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const foundUser = userExists(res, users, _id);
|
||||
if (foundUser === null) return;
|
||||
|
||||
await users.deleteOne({_id});
|
||||
|
||||
res.status(200).send();
|
||||
}
|
||||
|
||||
async function userlist(req, res) {
|
||||
const result = await users.find().project({
|
||||
username: 1,
|
||||
role: 1
|
||||
}).toArray();
|
||||
|
||||
res.status(200).send(result);
|
||||
}
|
||||
|
||||
async function resetpassword(req, res) {
|
||||
/** @type {string} */
|
||||
const userid = req.body.userid;
|
||||
const _id = new ObjectId(userid);
|
||||
|
||||
const foundUser = userExists(res, users, _id);
|
||||
if (foundUser === null) return;
|
||||
|
||||
const password = generateSessionToken(10);
|
||||
|
||||
await updatePassword(users, _id, password, false);
|
||||
res.status(200).send({
|
||||
_id: userid,
|
||||
username: foundUser.username,
|
||||
role: foundUser.role,
|
||||
password
|
||||
});
|
||||
}
|
||||
|
||||
async function changerole(req, res) {
|
||||
/** @type {string} */
|
||||
const userid = req.body.userid;
|
||||
const _id = new ObjectId(userid);
|
||||
const newrole = req.body.role;
|
||||
|
||||
if (!isValidRole(newrole)) {
|
||||
res.status(400).send("No valid role");
|
||||
return;
|
||||
}
|
||||
|
||||
const foundUser = await userExists(res, users, _id);
|
||||
if (foundUser === null) return;
|
||||
|
||||
await users.updateOne({_id}, {
|
||||
$set: {
|
||||
role: newrole
|
||||
}
|
||||
});
|
||||
|
||||
res.status(200).send({
|
||||
_id,
|
||||
username: foundUser.username,
|
||||
role: newrole
|
||||
});
|
||||
}
|
||||
|
||||
function getRoles(req, res) {
|
||||
res.status(200).send(roles);
|
||||
}
|
||||
|
||||
async function changePassword(req, res) {
|
||||
const oldpassword = req.body.old;
|
||||
const newpassword = req.body.new;
|
||||
|
||||
const userobj = await authenticateUser(req.user.username, oldpassword, false);
|
||||
|
||||
if (userobj === null) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
await updatePassword(users, req.user._id, newpassword, false);
|
||||
|
||||
res.status(200).send();
|
||||
}
|
||||
|
||||
async function logoutUser(req, res) {
|
||||
await users.updateOne({_id: req.user._id}, {
|
||||
$set: {
|
||||
sessiontoken: ""
|
||||
}
|
||||
});
|
||||
|
||||
res.status(200).send();
|
||||
}
|
||||
76
src/userHelper.js
Normal file
76
src/userHelper.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { pbkdf2Sync, randomBytes } from "node:crypto";
|
||||
|
||||
export async function createUser(collection, username, password, role, withSession = true) {
|
||||
const {salt, iterations, hash} = createHash(password);
|
||||
|
||||
let sessiontoken = "";
|
||||
if (withSession) {
|
||||
sessiontoken = generateSessionToken();
|
||||
}
|
||||
|
||||
const result = await collection.insertOne({
|
||||
username,
|
||||
role,
|
||||
salt,
|
||||
iterations,
|
||||
hash,
|
||||
sessiontoken
|
||||
});
|
||||
|
||||
return {sessiontoken, username, role, _id: result.insertedId};
|
||||
}
|
||||
|
||||
export async function updatePassword(collection, _id, password, keepSession = true) {
|
||||
const {salt, iterations, hash} = createHash(password);
|
||||
|
||||
if (keepSession) {
|
||||
await collection.updateOne({_id}, {$set: {
|
||||
salt,
|
||||
iterations,
|
||||
hash
|
||||
}});
|
||||
} else {
|
||||
await collection.updateOne({_id}, {$set: {
|
||||
salt,
|
||||
iterations,
|
||||
hash,
|
||||
sessiontoken: ""
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
export function generateSessionToken(length = 128, encoding = 'base64') {
|
||||
return randomBytes(length).toString(encoding);
|
||||
}
|
||||
|
||||
export function generateHash(password, salt, iterations) {
|
||||
return pbkdf2Sync(password, salt, iterations, 128, 'sha512').toString('hex');
|
||||
}
|
||||
|
||||
export async function updateSessionToken(collection, _id) {
|
||||
const sessiontoken = generateSessionToken();
|
||||
await collection.updateOne({_id: _id}, {$set: {
|
||||
sessiontoken
|
||||
}});
|
||||
return sessiontoken;
|
||||
}
|
||||
|
||||
export async function userExists(res, collection, _id) {
|
||||
const foundUser = await collection.findOne({_id});
|
||||
|
||||
if (foundUser === null) {
|
||||
res.status(400).send();
|
||||
}
|
||||
|
||||
return foundUser;
|
||||
}
|
||||
|
||||
function createHash(password) {
|
||||
const salt = randomBytes(128).toString('base64');
|
||||
const iterations = Math.floor(Math.random() * 5000) + 5000;
|
||||
const hash = generateHash(password, salt, iterations);
|
||||
|
||||
return {
|
||||
salt, hash, iterations
|
||||
}
|
||||
}
|
||||
37
src/util.js
Normal file
37
src/util.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
*
|
||||
* @param {any} body
|
||||
* @param {string} property
|
||||
*/
|
||||
export function checkStringProp(body, property) {
|
||||
if (body === undefined) return false;
|
||||
if (Object.hasOwn(body, property)) {
|
||||
if (typeof body[property] === 'string') {
|
||||
return body[property].length > 0;
|
||||
} else return false;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} body
|
||||
* @param {string} property
|
||||
*/
|
||||
export function checkNumberProp(body, property) {
|
||||
if (body === undefined) return false;
|
||||
if (Object.hasOwn(body, property)) {
|
||||
return typeof body[property] === 'number';
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} body
|
||||
* @param {string} property
|
||||
*/
|
||||
export function checkObjectProp(body, property) {
|
||||
if (body === undefined) return false;
|
||||
if (Object.hasOwn(body, property)) {
|
||||
return typeof body[property] === 'object';
|
||||
} else return false;
|
||||
}
|
||||
Reference in New Issue
Block a user