Merge pull request 'Lägg till autentisering och loggning, refaktorisera uppgifter till router' (#9) from cleanup into main

Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
2025-01-24 21:26:50 +01:00
7 changed files with 213 additions and 94 deletions

View File

@@ -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"

98
logger.js Normal file
View File

@@ -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;

16
middleware/auth.js Normal file
View File

@@ -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;

12
package-lock.json generated
View File

@@ -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",

View File

@@ -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"
}

79
routes/tasks.js Normal file
View File

@@ -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;

View File

@@ -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}`);