From 6a34f4c70d059633f9049f1ddfa9884beefcc93e Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 21 Nov 2021 13:25:24 +0100 Subject: [PATCH 01/14] fix: daily query limit (#69) --- CHANGELOG.md | 7 +++++++ database/models/PackageProgress.js | 2 +- package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9030b5e..3e07377a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This changelog goes through all the changes that have been made in each release on the [vocascan-server](https://github.com/vocascan/vocascan-server). +## [v1.1.1](https://github.com/vocascan/vocascan-server/releases/tag/v1.1.1) - 2021.11.21 + +In this bug-fixing version the Daily query limit is fixed, which due to a code error, took the date when the server was started. + +- Bugfixes + - Daily query limit (#69) + ## [v1.1.0](https://github.com/vocascan/vocascan-server/releases/tag/v1.1.0) - 2021.11.20 After some time, the new version of Vocascan Server has finally been released, with import/export features to share your diff --git a/database/models/PackageProgress.js b/database/models/PackageProgress.js index 6dbc6d10..cc859af6 100644 --- a/database/models/PackageProgress.js +++ b/database/models/PackageProgress.js @@ -20,7 +20,7 @@ module.exports = (sequelize) => { }, date: { type: DataTypes.DATEONLY, - defaultValue: new Date(), + defaultValue: () => new Date(), allowNull: false, }, learnedTodayCorrect: { diff --git a/package.json b/package.json index ea6e8088..2934665b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vocascan/server", - "version": "1.1.0", + "version": "1.1.1", "description": "A highly configurable vocabulary trainer", "main": "./server.js", "scripts": { From 3325e8f42adbf270ad92916c0f20c1d36e9e532c Mon Sep 17 00:00:00 2001 From: luwol03 Date: Thu, 30 Dec 2021 14:14:55 +0100 Subject: [PATCH 02/14] adjusted version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2934665b..a02df575 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vocascan/server", - "version": "1.1.1", + "version": "1.1.1-rc.1", "description": "A highly configurable vocabulary trainer", "main": "./server.js", "scripts": { From b889acc4e0bfe35bdc611e2aec7ca191f6376094 Mon Sep 17 00:00:00 2001 From: luwol03 <60048565+luwol03@users.noreply.github.com> Date: Fri, 31 Dec 2021 23:23:57 +0100 Subject: [PATCH 03/14] CORS (#71) * added cors origin config option --- app/Middleware/SecurityMiddleware.js | 9 +++++++++ app/config/config/schema.js | 1 + app/config/joi.js | 25 ++++++++++++++++++------- package.json | 1 + server.js | 4 ++++ vocascan.config.example.js | 1 + 6 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 app/Middleware/SecurityMiddleware.js diff --git a/app/Middleware/SecurityMiddleware.js b/app/Middleware/SecurityMiddleware.js new file mode 100644 index 00000000..8ebd9a2f --- /dev/null +++ b/app/Middleware/SecurityMiddleware.js @@ -0,0 +1,9 @@ +const cors = require('cors'); + +const config = require('../config/config'); + +const CorsMiddleware = cors({ + origin: config.server.cors, +}); + +module.exports = [CorsMiddleware]; diff --git a/app/config/config/schema.js b/app/config/config/schema.js index 79311494..417c30cb 100644 --- a/app/config/config/schema.js +++ b/app/config/config/schema.js @@ -44,6 +44,7 @@ const configSchema = Joi.object({ jwt_secret: Joi.string().required(), salt_rounds: Joi.number().integer().min(0).max(20).default(10), registration_locked: Joi.boolean().default(false), + cors: Joi.alternatives().try(Joi.boolean(), Joi.keyArray()).default(false), }).required(), database: Joi.object({ diff --git a/app/config/joi.js b/app/config/joi.js index 8e765ef7..fe2e6fb7 100644 --- a/app/config/joi.js +++ b/app/config/joi.js @@ -1,9 +1,20 @@ -const Joi = require('joi').extend((joi) => ({ - base: joi.array(), - type: 'stringArray', - coerce: (value) => ({ - value: value.split ? value.split(',') : value, - }), -})); +const Joi = require('joi') + .extend((joi) => ({ + base: joi.array(), + type: 'stringArray', + coerce: (value) => ({ + value: value.split ? value.split(',') : value, + }), + })) + .extend((joi) => ({ + base: joi.array(), + type: 'keyArray', + coerce: (value) => { + if (typeof value === 'object') { + return { value: Object.values(value) }; + } + return { value: [value] }; + }, + })); module.exports = Joi; diff --git a/package.json b/package.json index a02df575..83bbb102 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "cli-highlight": "^2.1.11", "commander": "^8.3.0", "console-table-printer": "^2.10.0", + "cors": "^2.8.5", "dotenv": "^8.1.0", "express": "^4.16.2", "http-status": "^1.5.0", diff --git a/server.js b/server.js index 7d844a28..2f49a1e7 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,7 @@ const runServer = async (extraConfig) => { const { errorConverter, errorHandler } = require('./app/Middleware/ErrorMiddleware.js'); const LoggingMiddleware = require('./app/Middleware/LoggingMiddleware'); + const SecurityMiddleware = require('./app/Middleware/SecurityMiddleware'); const ApiError = require('./app/utils/ApiError.js'); const routes = require('./routes'); @@ -32,6 +33,9 @@ const runServer = async (extraConfig) => { // logging middleware app.use(LoggingMiddleware); + // security + app.use(SecurityMiddleware); + // middleware app.use(express.json()); diff --git a/vocascan.config.example.js b/vocascan.config.example.js index 52cd015d..6026618c 100644 --- a/vocascan.config.example.js +++ b/vocascan.config.example.js @@ -11,6 +11,7 @@ module.exports = { jwt_secret: '', salt_rounds: 10, registration_locked: false, + cors: ['https://web.example1.com', 'https://web.example2.com'], }, database: { From 842bd23897d76ceaa77f9d350efd96bd89c087a3 Mon Sep 17 00:00:00 2001 From: luwol03 <60048565+luwol03@users.noreply.github.com> Date: Sun, 2 Jan 2022 07:30:17 +0100 Subject: [PATCH 04/14] fix cors if cors includes * (#73) --- app/Middleware/SecurityMiddleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Middleware/SecurityMiddleware.js b/app/Middleware/SecurityMiddleware.js index 8ebd9a2f..34fb4368 100644 --- a/app/Middleware/SecurityMiddleware.js +++ b/app/Middleware/SecurityMiddleware.js @@ -3,7 +3,7 @@ const cors = require('cors'); const config = require('../config/config'); const CorsMiddleware = cors({ - origin: config.server.cors, + origin: typeof config.server.cors !== 'boolean' && config.server.cors.includes('*') ? '*' : config.server.cors, }); module.exports = [CorsMiddleware]; From 5706963717ef90cd6c8a4e908621a793e0c687f3 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 3 Jan 2022 09:47:24 +0100 Subject: [PATCH 05/14] Feature/legal templates (#72) --- .gitignore | 1 + app/Controllers/StaticPagesController.js | 48 ++++++++++++++++++++++++ app/config/config/schema.js | 16 ++++++++ routes/index.js | 3 ++ routes/pages.js | 15 ++++++++ 5 files changed, 83 insertions(+) create mode 100644 app/Controllers/StaticPagesController.js create mode 100644 routes/pages.js diff --git a/.gitignore b/.gitignore index 617ed4c2..1d297ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ vocascan.config.* !docker/**/vocascan.config.* docker/**/database *.log +/staticPages \ No newline at end of file diff --git a/app/Controllers/StaticPagesController.js b/app/Controllers/StaticPagesController.js new file mode 100644 index 00000000..1cc08deb --- /dev/null +++ b/app/Controllers/StaticPagesController.js @@ -0,0 +1,48 @@ +const catchAsync = require('../utils/catchAsync'); +const ApiError = require('../utils/ApiError.js'); +const httpStatus = require('http-status'); + +const renderPageHandler = catchAsync(async (res, page) => { + const options = { root: process.cwd() }; + + // check if page should be rendered from a static file or should be redirected to another one + if (page.type === 'file') { + res.sendFile(page.location, options, (err) => { + if (err) { + throw new ApiError(httpStatus.NOT_FOUND, 'Page was not found'); + } + }); + } else if (page.type === 'redirect') { + res.redirect(page.location); + } else { + throw new ApiError(httpStatus.NOT_FOUND, 'Unknown page type'); + } +}); + +const getRenderPageHandler = (pageValues) => + catchAsync(async (req, res) => { + // check which language the user wants his page + const language = req.query.lang; + + // if no language is given, render fallback page + if (!language || !pageValues.langs) { + renderPageHandler(res, pageValues.fallback); + + return; + } + + const languagePage = pageValues.langs[language]; + + // check if key (language) is available + if (languagePage) { + renderPageHandler(res, languagePage); + } + // requested page language is not defined by host, redirect to fallback page + else { + res.redirect(pageValues.url); + } + }); + +module.exports = { + getRenderPageHandler, +}; diff --git a/app/config/config/schema.js b/app/config/config/schema.js index 417c30cb..133af708 100644 --- a/app/config/config/schema.js +++ b/app/config/config/schema.js @@ -35,6 +35,11 @@ const logSchema = Joi.object({ archive_logs: Joi.boolean().when('mode', { is: 'file', otherwise: Joi.forbidden() }), }); +const renderPageSchema = Joi.object({ + type: Joi.string().valid('file', 'redirect').required(), + location: Joi.string().required(), +}).required(); + // main schema const configSchema = Joi.object({ debug: Joi.boolean().default(false), @@ -81,6 +86,17 @@ const configSchema = Joi.object({ api: Joi.object({ enable_swagger: Joi.boolean().default(true), }).default({}), + + pages: Joi.object() + .unknown() + .pattern( + Joi.string(), + Joi.object({ + url: Joi.string().required(), + fallback: renderPageSchema, + langs: Joi.object().unknown().pattern(Joi.string(), renderPageSchema), + }) + ), }); module.exports = configSchema; diff --git a/routes/index.js b/routes/index.js index ec1d7531..932eaac8 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,7 @@ const express = require('express'); const APIRouter = require('./api'); +const PageRouter = require('./pages'); const router = express.Router(); @@ -8,6 +9,8 @@ router.get('/', (_req, res) => { res.send('Hello World!'); }); +router.use('/', PageRouter); + router.use('/api', APIRouter); module.exports = router; diff --git a/routes/pages.js b/routes/pages.js new file mode 100644 index 00000000..81e9bb0b --- /dev/null +++ b/routes/pages.js @@ -0,0 +1,15 @@ +const express = require('express'); + +const config = require('../app/config/config'); + +const StaticPagesController = require('../app/Controllers/StaticPagesController.js'); + +const router = express.Router(); + +if (config.pages) { + Object.values(config.pages).forEach((page) => { + router.get(page.url, StaticPagesController.getRenderPageHandler(page)); + }); +} + +module.exports = router; From 1a1b335c86b97fb8d0e034be89f45201f20f8297 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 13 Jan 2022 16:57:49 +0100 Subject: [PATCH 06/14] fix: file path (#74) --- app/Controllers/StaticPagesController.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Controllers/StaticPagesController.js b/app/Controllers/StaticPagesController.js index 1cc08deb..7f13d9df 100644 --- a/app/Controllers/StaticPagesController.js +++ b/app/Controllers/StaticPagesController.js @@ -1,13 +1,12 @@ +const path = require('path'); const catchAsync = require('../utils/catchAsync'); const ApiError = require('../utils/ApiError.js'); const httpStatus = require('http-status'); const renderPageHandler = catchAsync(async (res, page) => { - const options = { root: process.cwd() }; - // check if page should be rendered from a static file or should be redirected to another one if (page.type === 'file') { - res.sendFile(page.location, options, (err) => { + res.sendFile(path.resolve(process.cwd(), page.location), (err) => { if (err) { throw new ApiError(httpStatus.NOT_FOUND, 'Page was not found'); } From 769e77a7e40fa9b077a1780a9ec03f657e1c9c30 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 17 Jan 2022 19:11:53 +0100 Subject: [PATCH 07/14] Fix/traefik pathprefix (#75) --- docker/traefik/docker-compose.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docker/traefik/docker-compose.yml b/docker/traefik/docker-compose.yml index 2b64da03..eee9c759 100644 --- a/docker/traefik/docker-compose.yml +++ b/docker/traefik/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.8" +version: '3.8' services: traefik: image: traefik:v2.2 @@ -14,8 +14,8 @@ services: - ./traefik_dynamic.toml:/traefik_dynamic.toml - ./acme.json:/acme.json labels: - - "traefik.frontend.rule=Host:monitor.your_domain.com" - - "traefik.http.services.traefik.loadbalancer.server.port=8080" + - 'traefik.frontend.rule=Host:monitor.your_domain.com' + - 'traefik.http.services.traefik.loadbalancer.server.port=8080' networks: - web vocascan: @@ -25,27 +25,27 @@ services: depends_on: - db environment: - VOCASCAN_CONFIG: "/etc/vocascan/vocascan.config.js" + VOCASCAN_CONFIG: '/etc/vocascan/vocascan.config.js' volumes: - - "./vocascan.config.js:/etc/vocascan/vocascan.config.js:ro" + - './vocascan.config.js:/etc/vocascan/vocascan.config.js:ro' labels: - - "traefik.http.routers.vocascan.rule=Host(`web.your_domain.com`)" - - "traefik.http.routers.vocascan.tls=true" - - "traefik.http.routers.vocascan.tls.certresolver=lets-encrypt" - - "traefik.http.services.vocascan.loadbalancer.server.port=8000" + - 'traefik.http.routers.vocascan.rule=Host(`your_domain.com`) && PathPrefix(`/p/`, `/api`)' + - 'traefik.http.routers.vocascan.tls=true' + - 'traefik.http.routers.vocascan.tls.certresolver=lets-encrypt' + - 'traefik.http.services.vocascan.loadbalancer.server.port=8000' networks: - internal - web db: image: postgres environment: - POSTGRES_USER: "vocascan" - POSTGRES_PASSWORD: "vocascan" - POSTGRES_DB: "vocascan" + POSTGRES_USER: 'vocascan' + POSTGRES_PASSWORD: 'vocascan' + POSTGRES_DB: 'vocascan' volumes: - - "./database:/var/lib/postgresql/data" + - './database:/var/lib/postgresql/data' labels: - - "traefik.enable=false" + - 'traefik.enable=false' networks: - internal networks: From 47442ebbcb28589e5055784c4613d22c0b1f1569 Mon Sep 17 00:00:00 2001 From: luwol03 Date: Sat, 22 Jan 2022 10:40:55 +0100 Subject: [PATCH 08/14] push version to v1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83bbb102..e1005a90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vocascan/server", - "version": "1.1.1-rc.1", + "version": "1.2.0", "description": "A highly configurable vocabulary trainer", "main": "./server.js", "scripts": { From af25242070ca474d2b568a93cbf2eb17f1d6f99f Mon Sep 17 00:00:00 2001 From: luwol03 <60048565+luwol03@users.noreply.github.com> Date: Sat, 22 Jan 2022 10:44:07 +0100 Subject: [PATCH 09/14] v1.2.0 changelog (#76) Co-authored-by: noctera --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e07377a..642f6102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ This changelog goes through all the changes that have been made in each release on the [vocascan-server](https://github.com/vocascan/vocascan-server). +## [v1.2.0](https://github.com/vocascan/vocascan-server/releases/tag/v1.2.0) - 2022.01.22 + +In this release of vocascan-server we have added new features to make your server more secure. On the one hand, the legal side. You can now include static HTML pages or redirects to your privacy policy or imprint. On the other hand, in security, with an improved CORS configuration option to restrict access to only allowed frontend domains. + +- Features + - Feature/legal templates (#72) +- Security + - CORS (#71) + ## [v1.1.1](https://github.com/vocascan/vocascan-server/releases/tag/v1.1.1) - 2021.11.21 In this bug-fixing version the Daily query limit is fixed, which due to a code error, took the date when the server was started. From 9592599b40321b74c3b89c2fa11722bc9c276bd2 Mon Sep 17 00:00:00 2001 From: luwol03 Date: Sat, 22 Jan 2022 10:49:09 +0100 Subject: [PATCH 10/14] update version to v1.2.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e1005a90..d86532ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vocascan/server", - "version": "1.2.0", + "version": "1.2.0-rc.1", "description": "A highly configurable vocabulary trainer", "main": "./server.js", "scripts": { From 007e16d8c31fffd13ee51a190a65d82f262b5a19 Mon Sep 17 00:00:00 2001 From: Trite8Q1 <60318513+Trite8Q1@users.noreply.github.com> Date: Fri, 28 Jan 2022 02:32:23 -0500 Subject: [PATCH 11/14] closes Update Swagger Documentation for password reset #80 (#81) --- docs/api/swagger.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/swagger.json b/docs/api/swagger.json index 9b7fa711..c043c537 100644 --- a/docs/api/swagger.json +++ b/docs/api/swagger.json @@ -125,7 +125,7 @@ } } }, - "/user/password/reset": { + "/user/reset-password": { "post": { "summary": "Reset user password", "tags": ["auth"], From ba19d398775a16b5c443779a1d4a6db6fb67786a Mon Sep 17 00:00:00 2001 From: Trite8Q1 <60318513+Trite8Q1@users.noreply.github.com> Date: Fri, 28 Jan 2022 07:05:39 -0500 Subject: [PATCH 12/14] Added Password Complexity to register and password reset (#79) --- app/Controllers/AuthController.js | 11 +++++++++++ app/Services/AuthServiceProvider.js | 22 ++++++++++++++++++++++ app/utils/index.js | 9 +++++++++ 3 files changed, 42 insertions(+) diff --git a/app/Controllers/AuthController.js b/app/Controllers/AuthController.js index de31523c..82a52996 100644 --- a/app/Controllers/AuthController.js +++ b/app/Controllers/AuthController.js @@ -1,19 +1,26 @@ +const httpStatus = require('http-status'); const config = require('../config/config'); const { createUser, loginUser, validateRegister, validateLogin, + validatePassword, destroyUser, changePassword, } = require('../Services/AuthServiceProvider'); const { useInviteCode } = require('../Services/InviteCodeProvider'); const { generateJWT, deleteKeysFromObject } = require('../utils'); const catchAsync = require('../utils/catchAsync'); +const ApiError = require('../utils/ApiError'); const register = catchAsync(async (req, res) => { await validateRegister(req, res); + if (!validatePassword(req.body.password)) { + throw new ApiError(httpStatus.BAD_REQUEST, 'password complexity failed', 'password'); + } + const user = await createUser(req.body); const token = generateJWT({ id: user.id }, config.server.jwt_secret); @@ -56,6 +63,10 @@ const resetPassword = catchAsync(async (req, res) => { const userId = req.user.id; const { oldPassword, newPassword } = req.body; + if (!validatePassword(req.body.newPassword)) { + throw new ApiError(httpStatus.BAD_REQUEST, 'password complexity failed', 'newPassword'); + } + await changePassword(userId, oldPassword, newPassword); res.status(204).end(); diff --git a/app/Services/AuthServiceProvider.js b/app/Services/AuthServiceProvider.js index 529290cd..56d5fd9a 100644 --- a/app/Services/AuthServiceProvider.js +++ b/app/Services/AuthServiceProvider.js @@ -4,6 +4,7 @@ const crypto = require('crypto'); const { deleteKeysFromObject, hashEmail } = require('../utils'); const { User, Role } = require('../../database'); const ApiError = require('../utils/ApiError.js'); +const { bytesLength } = require('../utils/index.js'); const httpStatus = require('http-status'); const { validateInviteCode } = require('../Services/InviteCodeProvider.js'); const config = require('../config/config'); @@ -82,6 +83,26 @@ function validateLogin(req, res) { return validateAuth(req, res); } +function validatePassword(password) { + const passwordLength = bytesLength(password); + + const length = passwordLength >= 8 && passwordLength <= 72; + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasNumbers = /\d/.test(password); + const hasNonAlphas = /\W/.test(password); + + let passwordComplexity = 0; + + if (length) { + passwordComplexity = length + hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas; + } else if (passwordLength !== 0) { + passwordComplexity = 1; + } + + return passwordLength >= 8 && passwordLength <= 72 && passwordComplexity >= 4; +} + // Create new user and store into database async function createUser({ username, email, password }) { // Hash password @@ -196,6 +217,7 @@ module.exports = { loginUser, validateRegister, validateLogin, + validatePassword, destroyUser, changePassword, checkPasswordValid, diff --git a/app/utils/index.js b/app/utils/index.js index 7be9ba93..db93b282 100644 --- a/app/utils/index.js +++ b/app/utils/index.js @@ -286,6 +286,14 @@ const askToConfirm = (question) => { }); }; +/** + * Return the length of a string in bytes + * See: https://stackoverflow.com/questions/5515869/string-length-in-bytes-in-javascript + * @param {String} string string + * @returns bytesLength + */ +const bytesLength = (string) => new TextEncoder().encode(string).length; + module.exports = { generateJWT, parseTokenUserId, @@ -307,4 +315,5 @@ module.exports = { truncateString, hashEmail, askToConfirm, + bytesLength, }; From 9679685bf13ed410860c015c59d5427ed8229841 Mon Sep 17 00:00:00 2001 From: luwol03 <60048565+luwol03@users.noreply.github.com> Date: Sat, 29 Jan 2022 18:01:50 +0100 Subject: [PATCH 13/14] v1.2.1 changelog (#82) --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 642f6102..74640782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ This changelog goes through all the changes that have been made in each release on the [vocascan-server](https://github.com/vocascan/vocascan-server). +## [v1.2.1](https://github.com/vocascan/vocascan-server/releases/tag/v1.2.1) - 2022.01.29 + +In this release of vocascan-server we have added new security features to force secure passwords to the users. + +- Features + - Added Password Complexity to register and password reset (#79) +- Bug + - closes Update Swagger Documentation for password reset (#80) (#81) + ## [v1.2.0](https://github.com/vocascan/vocascan-server/releases/tag/v1.2.0) - 2022.01.22 In this release of vocascan-server we have added new features to make your server more secure. On the one hand, the legal side. You can now include static HTML pages or redirects to your privacy policy or imprint. On the other hand, in security, with an improved CORS configuration option to restrict access to only allowed frontend domains. From d5398ffdb658dab6ed431783e8290b2e1302e36d Mon Sep 17 00:00:00 2001 From: luwol03 Date: Sat, 29 Jan 2022 18:25:50 +0100 Subject: [PATCH 14/14] incremented version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d86532ab..87c1de4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vocascan/server", - "version": "1.2.0-rc.1", + "version": "1.2.1", "description": "A highly configurable vocabulary trainer", "main": "./server.js", "scripts": {