From acefa334a55aec60e91b500aa2cf05484a5c6a50 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sat, 15 Feb 2025 02:27:10 -0500 Subject: [PATCH] implement validation of email and password, ratelimiting, add ci for builds --- .gitea/workflows/docker.yml | 30 +++++++++++++++++++++++ package.json | 5 +++- ratelimit.json | 10 ++++++++ server.js | 49 +++++++++++++++++++++++++++++++++++-- 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 .gitea/workflows/docker.yml create mode 100644 ratelimit.json diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml new file mode 100644 index 0000000..1f9343c --- /dev/null +++ b/.gitea/workflows/docker.yml @@ -0,0 +1,30 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build_and_push: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Log in to Gitea Package Registry + run: echo "${{ secrets.PACKAGE_TOKEN }}" | docker login $SERVER_URL -u $USERNAME --password-stdin + env: + SERVER_URL: ${{ secrets.SERVER_URL }} + USERNAME: ${{ secrets.USERNAME }} + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + + - name: Build Docker Image + run: docker build -t git.pontusmail.org/librecloud/mail-connect:latest . + + - name: Push Docker Image + run: docker push git.pontusmail.org/librecloud/mail-connect:latest diff --git a/package.json b/package.json index e445c4a..e87b044 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,10 @@ "dependencies": { "child_process": "^1.0.2", "dockerode": "^4.0.4", - "express": "^4.21.2" + "express": "^4.21.2", + "express-rate-limit": "^7.5.0", + "password-validator": "^5.3.0", + "validator": "^13.12.0" }, "trustedDependencies": [ "protobufjs" diff --git a/ratelimit.json b/ratelimit.json new file mode 100644 index 0000000..ae8b433 --- /dev/null +++ b/ratelimit.json @@ -0,0 +1,10 @@ +{ + "/list": { + "windowMs": 60000, + "max": 20 + }, + "/add": { + "windowMs": 60000, + "max": 10 + } +} \ No newline at end of file diff --git a/server.js b/server.js index bd7508c..2c436fa 100644 --- a/server.js +++ b/server.js @@ -1,10 +1,53 @@ const express = require('express'); +const fs = require('fs'); +const rateLimit = require('express-rate-limit'); const Docker = require('dockerode'); +const validator = require('validator'); const docker = new Docker({ socketPath: '/var/run/docker.sock' }); const app = express(); app.use(express.json()); +// Rate limiting + +let rateLimitConfig = {}; +try { + const data = fs.readFileSync('./ratelimit.json', 'utf8'); + rateLimitConfig = JSON.parse(data); +} catch (err) { + console.error('Error loading rate limit config:', err); +} + +function createLimiter(options) { + return rateLimit({ + windowMs: options.windowMs, + max: options.max, + message: "Too many requests, please try again another time.", + }); +} + +if (rateLimitConfig['/list']) { + app.use('/list', createLimiter(rateLimitConfig['/list'])); +} +if (rateLimitConfig['/add']) { + app.use('/add', createLimiter(rateLimitConfig['/add'])); +} + +// Utility fxns + +function validateEmail(email) { + return typeof email === 'string' && validator.isEmail(email); +} + +const PasswordValidator = require('password-validator'); +const passwordSchema = new PasswordValidator(); +passwordSchema + .is().min(8) + .is().max(64) + .has().letters() + .has().digits() + .has().not().spaces(); + function listAccounts() { return new Promise((resolve, reject) => { const container = docker.getContainer('mailserver'); @@ -49,6 +92,8 @@ function listAccounts() { }); } +// Routes + app.get('/list', (req, res) => { listAccounts() .then(accounts => res.json({ accounts })) @@ -57,8 +102,8 @@ app.get('/list', (req, res) => { app.post('/add', (req, res) => { const { email, password } = req.body; - if (!email || !password) { - return res.status(400).json({ error: "Email and password are required." }); + if (!email || !validateEmail(email) || !password) { + return res.status(400).json({ error: "A valid email and password is required." }); } const container = docker.getContainer('mailserver');