feat: check latex dependency on start & add security headers

This commit is contained in:
Nikolaj Fabricius-Bjerre 2024-03-05 13:08:25 +01:00
parent b359bb049e
commit fecf789f58
5 changed files with 43 additions and 4 deletions

14
app.js
View File

@ -2,13 +2,25 @@ const express = require('express');
const config = require('./config');
const app = express();
const path = require('path');
const { verifyLatexBinary } = require('./helpers/latex');
const helmet = require('helmet');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'templates'));
app.use('/', express.static(path.join(__dirname, 'public')));
// TODO: add security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
"script-src": ["'self'", "'unsafe-inline'", "https://unpkg.com/feather-icons"],
"script-src-attr": ["'self'", "'unsafe-inline'"]
},
},
}))
app.use('/', require('./controllers/index'));
// check if pdf binaries are installed
verifyLatexBinary();
app.listen(config.port, () => console.log(`pdfgen is listening on port ${config.port}`))

13
helpers/latex.js Normal file
View File

@ -0,0 +1,13 @@
const { spawnSync } = require('child_process');
function verifyLatexBinary() {
const { error } = spawnSync('latex', ['--version'], {encoding: 'utf8'});
if (error) {
console.error(error);
process.exit(1);
} else {
console.log('latex binaries seem ok');
}
}
module.exports = { verifyLatexBinary };

14
package-lock.json generated
View File

@ -12,6 +12,7 @@
"ejs": "^3.1.9",
"express": "^4.18.3",
"formidable": "^3.1.5",
"helmet": "^7.1.0",
"node-latex": "^3.1.0"
},
"devDependencies": {
@ -755,6 +756,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/helmet": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
"integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
@ -2335,6 +2344,11 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"helmet": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
"integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg=="
},
"hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",

View File

@ -9,13 +9,13 @@
"test": "echo \"Error: no test specified\" && exit 1",
"minify": "minify public/css.css > public/css.min.css && minify public/js.js > public/js.min.js",
"start": "node app.js",
"dev": "NODE_ENV=development nodemon -V .",
"tmux": "NODE_ENV=development ./tmux_dev.sh"
"dev": "NODE_ENV=development nodemon -V ."
},
"dependencies": {
"ejs": "^3.1.9",
"express": "^4.18.3",
"formidable": "^3.1.5",
"helmet": "^7.1.0",
"node-latex": "^3.1.0"
},
"devDependencies": {

2
public/js.min.js vendored
View File

@ -1 +1 @@
window.pdfgen={data:{},conf:{disabled:[]}};const $=e=>{const t=document.querySelectorAll(e);if(!t)throw new Error(`DOM elem '${e}' not found`);return 1==t.length?t[0]:t};function merge_obj(e,t){return e?(Object.keys(t).forEach((a=>{"object"!=typeof t[a]?e[a]=t[a]:e[a]=merge_obj(e[a],t[a])})),e):t}function error(e){console.log("error(): ",e),e.name&&e.message?alert(`${e.name}\n${e.message}`):"string"==typeof e?alert(e):console.error(e),e.el&&"object"==typeof e.el&&document.body.contains(e.el)&&setTimeout((()=>{e.el.focus(),e.el.select()}),100)}async function cvrapi(e){const t=e.value;let a=null;if(!/\d{8}/g.test(t))return error({el:e,name:"Forkert CVR format",message:" "});const r=await fetch(`/api/cvr/${t}`);try{a=await r.json()}catch(t){return console.log("json error:",t),error({el:e,name:"Kunne ikke parse respons body til JSON",message:"Serveren svarede ikke korrekt, skriv gerne en fejlrapport."})}return 404===r.status?error({el:e,name:"Kunne ikke finde et firma",message:`med CVR-nummer ${t}`}):r.ok?a:error({el:e,name:"Server error",message:e.error})}function format_date(e=new Date){const t="[object Date]"===Object.prototype.toString.call(e);if(t||"string"!=typeof e){if(!t)throw new Error("Invalid date!")}else try{e=new Date(e)}catch(e){throw console.error("Invalid date!"),e}return`${e.getDate()}/${e.getMonth()+1}/${e.getFullYear()}`}function populate(){return new Promise((async(e,t)=>{let a=Array.from(document.getElementsByClassName("var"));a=a.filter((e=>!window.pdfgen.conf.disabled.includes(e.id)));const r=(e,t)=>{if(e.includes(".")){[].concat(e.split("."));const a=e.split(".").reverse();let r={};r[a.shift()]=t,a.forEach(((e,t)=>{const a={};a[e]=r,r=a})),window.pdfgen.data=merge_obj(window.pdfgen.data,r)}else window.pdfgen.data[e]=t};await Promise.all(a.map((async e=>{if("number"===e.dataset.is)try{const t=parseInt(e.value);r(e.dataset.var,t)}catch(t){throw{el:e,name:"Client-side input validering",message:"Værdien kunne ikke konverteres til et tal"}}else if("boolean"===e.dataset.is){if("boolean"!=typeof e.checked)throw{el:e,name:"Client-side input validering",message:"Checkboxens værdi er ikke gyldig :S"};r(e.dataset.var,e.checked)}else if("string"===e.dataset.is){if("string"!=typeof e.value||""===e.value)throw{el:e,name:"Client-side input validering",message:"Teksten kunne ikke valideres"};r(e.dataset.var,e.value)}}))).then((()=>e())).catch((e=>(e.el&&(e.el.className+=" vali_err",setTimeout((()=>e.el.className=e.el.className.replace(/\ vali_err/g,"")),3800)),t(e))))}))}function modal_toggle(e){const t=document.querySelector(`#modal-${e}`);t.className.includes("show")?t.className=t.className.replace(/\ show/g,""):t.className+=" show"}window.addEventListener("DOMContentLoaded",(e=>{Array.from(document.querySelectorAll('input.var[type="file"]')).filter((e=>"image"===e.dataset.is)).forEach((e=>{e.addEventListener("change",(async t=>{let a;window.pdfgen.data.files||(window.pdfgen.data.files={});const r=new FormData;if(!e.files[0])throw{el:e,name:"Client-side input validering",message:"Du mangler at vælge en fil til upload"};r.append("file",e.files[0]);const n=await fetch("/pdf/upload/image",{method:"POST",body:r});try{a=await n.json()}catch(t){throw{el:e,name:"Kunne ikke parse respons body til JSON",message:"Serveren svarede ikke korrekt, skriv gerne en fejlrapport."}}if(!n.ok){if(!a.error)return reject(a);throw{el:e,name:"Kunne ikke uploade fil",message:a.error}}if(window.file_reset_timer&&setTimeout((()=>{delete window.pdfgen.data.files[e.dataset.var],e.dataset.preview&&($(`#${e.dataset.preview}`).innerHTML="")}),window.file_reset_timer),e.dataset.preview){const t=$(`#${e.dataset.preview}`);t.innerHTML="";const r=document.createElement("img");r.className="image_preview",r.src=`file/${a.fileid}`,t.appendChild(r)}window.pdfgen.data.files[e.dataset.var]=a.fileid}))}))}));
var A=a=>typeof a=='string',{keys:b}=Object,$=_=>{const B=document.querySelectorAll(_);if(!B)throw Error(`DOM elem '${_}' not found`);return (B.length==1)?B[0]:B};window.pdfgen={data:{},conf:{disabled:[]}};function d(c,C){if(!c)return C;for(const k of b(C))typeof C[k]!=='object'?c[k]=C[k]:c[k]=d(c[k],C[k]);return c}window.addEventListener('DOMContentLoaded',()=>{var _a=[...document.querySelectorAll('input.var[type="file"]')];_a.filter(D=>D.dataset.is=='image').forEach(_A=>_A.addEventListener('change',async ()=>{!window.pdfgen.data.files&&(window.pdfgen.data.files={});let e;var _b=new FormData();if(!_A.files[0])throw {el:_A,name:'Client-side input validering',message:'Du mangler at vælge en fil til upload'};_b.append('file',_A.files[0]);var _c=await fetch('/pdf/upload/image',{method:'POST',body:_b});try {e=await _c.json()} catch {throw {el:_A,name:'Kunne ikke parse respons body til JSON',message:'Serveren svarede ikke korrekt, skriv gerne en fejlrapport.'}}if(!_c.ok){if(!e.error)return reject(e);throw {el:_A,name:'Kunne ikke uploade fil',message:e.error}}window.file_reset_timer&&setTimeout(()=>{delete window.pdfgen.data.files[_A.dataset.var];if(_A.dataset.preview)$(`#${_A.dataset.preview}`).innerHTML=''},window.file_reset_timer);if(_A.dataset.preview){var _d=$(`#${_A.dataset.preview}`),E=document.createElement('img');_d.innerHTML='';E.className='image_preview';E.src=`file/${e.fileid}`;_d.appendChild(E)}window.pdfgen.data.files[_A.dataset.var]=e.fileid}))});