From e091da93e92a15911d437035fe5e3e0d95565256 Mon Sep 17 00:00:00 2001 From: Fredrik Wahlberg Date: Fri, 24 Jan 2025 21:26:06 +0100 Subject: [PATCH] =?UTF-8?q?L=C3=A4gg=20till=20autentisering=20och=20loggni?= =?UTF-8?q?ng,=20refaktorisera=20uppgifter=20till=20router?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 4 ++ logger.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++ middleware/auth.js | 16 ++++++++ package-lock.json | 12 ++++++ package.json | 1 + routes/tasks.js | 79 +++++++++++++++++++++++++++++++++++++ server.js | 97 ++------------------------------------------- 7 files changed, 213 insertions(+), 94 deletions(-) create mode 100644 logger.js create mode 100644 middleware/auth.js create mode 100644 routes/tasks.js diff --git a/docker-compose.yml b/docker-compose.yml index 1f66a3d..867128d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,10 @@ services: volumes: - /srv/swarm/org-todo-pwa/data:/data user: "1000:1000" + environment: + - DEBUG=app + - AUTH_USERNAME=fredrik + - AUTH_PASSWORD=apa deploy: labels: - "traefik.enable=true" diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..0f7366b --- /dev/null +++ b/logger.js @@ -0,0 +1,98 @@ +const { createLogger, format, transports } = require('winston'); + +const logger = createLogger({ + level: 'info', + format: format.combine( + format.timestamp(), + format.json() + ), + transports: [ + new transports.Console(), + new transports.File({ filename: 'app.log' }) + ] +}); + +module.exports = logger; + +// filepath: /home/fredrik/dev/org-todo-pwa/routes/tasks.js +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +const logger = require('../logger'); +const auth = require('../middleware/auth'); + +const router = express.Router(); +const dataDir = '/data'; + +// Ensure the /data directory exists +if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); +} + +// Ensure the tags.json file exists +const tagsFilePath = path.join(dataDir, 'tags.json'); +if (!fs.existsSync(tagsFilePath)) { + fs.writeFileSync(tagsFilePath, JSON.stringify([])); +} + +// Protect the /add-task endpoint with authentication +router.post('/add-task', auth, async (req, res) => { + const { subject, description, scheduled } = req.body; + const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); + + let orgFormattedData = ` +* TODO ${subject} + SCHEDULED: <${scheduled}> + :LOGBOOK: + - State "TODO" from "TODO" [${currentDateTime}] + :END: + `; + + if (description) { + orgFormattedData = ` +* TODO ${subject} + ${description} + SCHEDULED: <${scheduled}> + :LOGBOOK: + - State "TODO" from "TODO" [${currentDateTime}] + :END: + `; + } + + const filePath = path.join(dataDir, 'tasks.org'); + try { + await fs.promises.appendFile(filePath, orgFormattedData); + res.send({ message: 'Task added successfully!' }); + } catch (err) { + logger.error('Error writing to file:', err); + res.status(500).send('Error writing to file.'); + } +}); + +// Endpoint to save tags +router.post('/save-tags', auth, async (req, res) => { + const { tags } = req.body; + const filePath = path.join(dataDir, 'tags.json'); + try { + await fs.promises.writeFile(filePath, JSON.stringify(tags)); + res.send({ message: 'Tags saved successfully!' }); + } catch (err) { + logger.error('Error saving tags:', err); + res.status(500).send('Error saving tags.'); + } +}); + +// Endpoint to retrieve tags +router.get('/get-tags', auth, async (req, res) => { + const filePath = path.join(dataDir, 'tags.json'); + try { + const data = await fs.promises.readFile(filePath); + const tags = JSON.parse(data); + res.send(tags); + } catch (err) { + logger.error('Error retrieving tags:', err); + res.status(500).send('Error retrieving tags.'); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..2ed8e42 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,16 @@ +const basicAuth = require('basic-auth'); + +const auth = (req, res, next) => { + const user = basicAuth(req); + const username = process.env.AUTH_USERNAME; // Use environment variables + const password = process.env.AUTH_PASSWORD; // Use environment variables + + if (user && user.name === username && user.pass === password) { + return next(); + } else { + res.set('WWW-Authenticate', 'Basic realm="401"'); + return res.status(401).send('Authentication required.'); + } +}; + +module.exports = auth; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3fcf444..92dfda1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "basic-auth": "^2.0.1", "body-parser": "^1.20.3", "debug": "^4.4.0", + "dotenv": "^16.4.7", "express": "^4.21.2", "fs": "^0.0.1-security" } @@ -185,6 +186,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 0d5998f..489b9f0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "basic-auth": "^2.0.1", "body-parser": "^1.20.3", "debug": "^4.4.0", + "dotenv": "^16.4.7", "express": "^4.21.2", "fs": "^0.0.1-security" } diff --git a/routes/tasks.js b/routes/tasks.js new file mode 100644 index 0000000..456c308 --- /dev/null +++ b/routes/tasks.js @@ -0,0 +1,79 @@ +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +const debug = require('debug')('app'); +const auth = require('../middleware/auth'); + +const router = express.Router(); +const dataDir = '/data'; + +// Ensure the /data directory exists +if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); +} + +// Ensure the tags.json file exists +const tagsFilePath = path.join(dataDir, 'tags.json'); +if (!fs.existsSync(tagsFilePath)) { + fs.writeFileSync(tagsFilePath, JSON.stringify([])); +} + +// Protect the /add-task endpoint with authentication +router.post('/add-task', auth, async (req, res) => { + const { subject, description, scheduled } = req.body; + const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); + + let orgFormattedData = ` +* TODO ${subject} + SCHEDULED: <${scheduled}> + :LOGBOOK: + - State "TODO" from "TODO" [${currentDateTime}] + :END:`; + + if (description) { + orgFormattedData = ` +* TODO ${subject} + ${description} + SCHEDULED: <${scheduled}> + :LOGBOOK: + - State "TODO" from "TODO" [${currentDateTime}] + :END:`; + } + + const filePath = path.join(dataDir, 'tasks.org'); + try { + await fs.promises.appendFile(filePath, orgFormattedData); + res.send({ message: 'Task added successfully!' }); + } catch (err) { + debug('Error writing to file:', err); + res.status(500).send('Error writing to file.'); + } +}); + +// Endpoint to save tags +router.post('/save-tags', auth, async (req, res) => { + const { tags } = req.body; + const filePath = path.join(dataDir, 'tags.json'); + try { + await fs.promises.writeFile(filePath, JSON.stringify(tags)); + res.send({ message: 'Tags saved successfully!' }); + } catch (err) { + debug('Error saving tags:', err); + res.status(500).send('Error saving tags.'); + } +}); + +// Endpoint to retrieve tags +router.get('/get-tags', auth, async (req, res) => { + const filePath = path.join(dataDir, 'tags.json'); + try { + const data = await fs.promises.readFile(filePath); + const tags = JSON.parse(data); + res.send(tags); + } catch (err) { + debug('Error retrieving tags:', err); + res.status(500).send('Error retrieving tags.'); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js index 074f584..e4fc37e 100644 --- a/server.js +++ b/server.js @@ -1,106 +1,15 @@ +require('dotenv').config(); const express = require('express'); const bodyParser = require('body-parser'); -const fs = require('fs'); -const path = require('path'); -const basicAuth = require('basic-auth'); const debug = require('debug')('app'); +const tasksRouter = require('./routes/tasks'); const app = express(); const port = 3044; app.use(bodyParser.json()); app.use(express.static('public')); - -// Ensure the /data directory exists -const dataDir = '/data'; -if (!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir, { recursive: true }); -} - -// Ensure the tags.json file exists -const tagsFilePath = path.join(dataDir, 'tags.json'); -if (!fs.existsSync(tagsFilePath)) { - fs.writeFileSync(tagsFilePath, JSON.stringify([])); -} - -// Authentication middleware -const auth = (req, res, next) => { - const user = basicAuth(req); - const username = 'fredrik'; // Replace with your desired username - const password = 'apa'; // Replace with your desired password - - if (user && user.name === username && user.pass === password) { - return next(); - } else { - res.set('WWW-Authenticate', 'Basic realm="401"'); - return res.status(401).send('Authentication required.'); - } -}; - -// Protect the /add-task endpoint with authentication -app.post('/add-task', auth, (req, res) => { - const { subject, description, scheduled } = req.body; - const currentDateTime = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); - - let orgFormattedData = ` -* TODO ${subject} - SCHEDULED: <${scheduled}> - :LOGBOOK: - - State "TODO" from "TODO" [${currentDateTime}] - :END: - `; - - if (description) { - orgFormattedData = ` -* TODO ${subject} - ${description} - SCHEDULED: <${scheduled}> - :LOGBOOK: - - State "TODO" from "TODO" [${currentDateTime}] - :END: - `; - } - - const filePath = path.join(dataDir, 'tasks.org'); - fs.appendFile(filePath, orgFormattedData, (err) => { - if (err) { - debug('Error writing to file:', err); - return res.status(500).send('Error writing to file.'); - } - res.send({ message: 'Task added successfully!' }); - }); -}); - -// Endpoint to save tags -app.post('/save-tags', auth, (req, res) => { - const { tags } = req.body; - const filePath = path.join(dataDir, 'tags.json'); - fs.writeFile(filePath, JSON.stringify(tags), (err) => { - if (err) { - debug('Error saving tags:', err); - return res.status(500).send('Error saving tags.'); - } - res.send({ message: 'Tags saved successfully!' }); - }); -}); - -// Endpoint to retrieve tags -app.get('/get-tags', auth, (req, res) => { - const filePath = path.join(dataDir, 'tags.json'); - fs.readFile(filePath, (err, data) => { - if (err) { - debug('Error retrieving tags:', err); - return res.status(500).send('Error retrieving tags.'); - } - try { - const tags = JSON.parse(data); - res.send(tags); - } catch (e) { - debug('Error parsing tags JSON:', e); - res.send([]); - } - }); -}); +app.use('/', tasksRouter); app.listen(port, () => { debug(`Server running at http://localhost:${port}`);