u
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Server
|
# Server
|
||||||
PORT=4208
|
PORT=4208
|
||||||
|
# HTTPS server port (defaults to 8443, set to 0 or false to disable)
|
||||||
|
HTTPS_PORT=8443
|
||||||
|
# Optional custom TLS cert/key paths (defaults to auto-generated certs/key.pem and certs/cert.pem)
|
||||||
|
# HTTPS_KEY_PATH=./certs/key.pem
|
||||||
|
# HTTPS_CERT_PATH=./certs/cert.pem
|
||||||
BODY_LIMIT=100mb
|
BODY_LIMIT=100mb
|
||||||
|
|
||||||
# CORS — comma-separated allowed origins
|
# CORS — comma-separated allowed origins
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules/
|
|||||||
data/
|
data/
|
||||||
logs/
|
logs/
|
||||||
print_spool/
|
print_spool/
|
||||||
|
certs/
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
http://localhost:4208/api
|
http://localhost:4208/api
|
||||||
|
https://localhost:8443/api
|
||||||
```
|
```
|
||||||
|
|
||||||
## CORS & cookies
|
## CORS & cookies
|
||||||
|
|||||||
301
package-lock.json
generated
301
package-lock.json
generated
@@ -26,6 +26,7 @@
|
|||||||
"multer": "^2.4.0",
|
"multer": "^2.4.0",
|
||||||
"react": "^19.3.0",
|
"react": "^19.3.0",
|
||||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||||
|
"selfsigned": "^5.5.0",
|
||||||
"sharp": "^0.35.4"
|
"sharp": "^0.35.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -708,6 +709,205 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-cms": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-CkX0H4NCIOMHOU3rh2xXZywinYZ/EnJlaOlJiGI0e5L4XjFqHf4iH30LMbXWChVppdA9ljbanjtzQFOhDNfTWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509-attr": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-csr": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-jTPTr/9rxKM+niQLMF3jiAMb0lWHUhUHIuSOhdGCIfpB4FAQYf9SoiXOgxP8bS/68IB+bj+1OHvVnDVCQfVUZw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-ecc": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-GFd3iOjFrWX+QWH2R2dO5QSJyyRGv9CIBKtRlGlPNCvb2RvmCbBDexe91MJgV76c69d998Xsa+CvuQ98seoiPg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-pfx": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-1y3QK9ZH1IPleAMmRoJlPS10eGkAWnULETW8zDFNUK1ffqpG7zmDY+kYBMYQgwlcQZ2iitLm2z2EBP0xzRXpaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-cms": "^2.10.0",
|
||||||
|
"@peculiar/asn1-pkcs8": "^2.10.0",
|
||||||
|
"@peculiar/asn1-rsa": "^2.10.0",
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-pkcs8": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-Ri+BZT9bnwqlHWmhs7lvWjvJRxKev2hA2+4EbZajgeYWCbR8hyv0znF4DhsFouOhMj2nSDV/iGJ6DMQtwITnCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-pkcs9": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-XIXsbDQFezYk6fudczkuvawkRD4GNpISGCqfYhdkJlvcGF/RFknU+0DDJagOaJqBf+PdPzk4J9oMqDGcvfR9HQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-cms": "^2.10.0",
|
||||||
|
"@peculiar/asn1-pfx": "^2.10.0",
|
||||||
|
"@peculiar/asn1-pkcs8": "^2.10.0",
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509-attr": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-rsa": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-4Jvmwlh3gZAhNZ4/u7JSyLuce9hofOFZ4o3PYKAccCImGnyaT5CMym/ATgvAvqpOYFNW531oPZvJlEBhJy9VfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-schema": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/utils": "^2.0.2",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-x509": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-ucNVg8+ANveTpMN3fy9lA2alryONdXc2A4cEG2hMniWbvQt+YOZoe8BI80YYNO8FBcuDY1qbDQ4uQGQVraHxGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/utils": "^2.0.2",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/asn1-x509-attr": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-/85GtKOKmgvuSJNlaFfwGWNdRSZZ+hpF02NyM01XiCdzpaZONiBltDyfluFPvFx966CR+ZHNSG1jniwpy07oGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.10.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.10.0",
|
||||||
|
"asn1js": "^3.0.10",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/utils": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/x509": {
|
||||||
|
"version": "1.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz",
|
||||||
|
"integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-cms": "^2.6.0",
|
||||||
|
"@peculiar/asn1-csr": "^2.6.0",
|
||||||
|
"@peculiar/asn1-ecc": "^2.6.0",
|
||||||
|
"@peculiar/asn1-pkcs9": "^2.6.0",
|
||||||
|
"@peculiar/asn1-rsa": "^2.6.0",
|
||||||
|
"@peculiar/asn1-schema": "^2.6.0",
|
||||||
|
"@peculiar/asn1-x509": "^2.6.0",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
|
"tsyringe": "^4.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -821,6 +1021,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1js": {
|
||||||
|
"version": "3.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
|
||||||
|
"integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"pvutils": "^1.1.5",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/auto-bind": {
|
"node_modules/auto-bind": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
|
||||||
@@ -1009,6 +1223,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bytestreamjs": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
@@ -2918,6 +3141,26 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pkijs": {
|
||||||
|
"version": "3.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz",
|
||||||
|
"integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"workspaces": [
|
||||||
|
"website"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.8.0",
|
||||||
|
"asn1js": "^3.0.6",
|
||||||
|
"bytestreamjs": "^2.0.1",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"pvutils": "^1.1.3",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pngjs": {
|
"node_modules/pngjs": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||||
@@ -2987,6 +3230,24 @@
|
|||||||
"once": "^1.3.1"
|
"once": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pvtsutils": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pvutils": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qrcode": {
|
"node_modules/qrcode": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||||
@@ -3142,6 +3403,12 @@
|
|||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/reflect-metadata": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -3231,6 +3498,19 @@
|
|||||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/selfsigned": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/x509": "^1.14.2",
|
||||||
|
"pkijs": "^3.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.8.5",
|
"version": "7.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
@@ -3709,8 +3989,25 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD",
|
"license": "0BSD"
|
||||||
"optional": true
|
},
|
||||||
|
"node_modules/tsyringe": {
|
||||||
|
"version": "4.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz",
|
||||||
|
"integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsyringe/node_modules/tslib": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||||
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/tunnel-agent": {
|
"node_modules/tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"multer": "^2.4.0",
|
"multer": "^2.4.0",
|
||||||
"react": "^19.3.0",
|
"react": "^19.3.0",
|
||||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||||
|
"selfsigned": "^5.5.0",
|
||||||
"sharp": "^0.35.4"
|
"sharp": "^0.35.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import http from 'http';
|
||||||
|
import https from 'https';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
@@ -9,6 +11,7 @@ import env from '../config/env.js';
|
|||||||
import { authMiddleware } from './middleware/auth.js';
|
import { authMiddleware } from './middleware/auth.js';
|
||||||
import { ensureConnection } from '../db/store.js';
|
import { ensureConnection } from '../db/store.js';
|
||||||
import { closeAll as closeAllSse } from './sse.js';
|
import { closeAll as closeAllSse } from './sse.js';
|
||||||
|
import { getOrCreateCert } from '../util/cert.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -36,6 +39,7 @@ async function loadRoutes(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
|
let httpsServer;
|
||||||
let starting;
|
let starting;
|
||||||
let startedAt;
|
let startedAt;
|
||||||
|
|
||||||
@@ -95,8 +99,34 @@ async function createApi() {
|
|||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listenServer(instance, port, protocol) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
instance.once('listening', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.once('error', (err) => {
|
||||||
|
if (err.code === 'EADDRINUSE') {
|
||||||
|
let detail = '';
|
||||||
|
try {
|
||||||
|
const pids = execSync(`lsof -ti :${port}`, { encoding: 'utf8' }).trim();
|
||||||
|
if (pids) detail = ` (PID ${pids.replace(/\n/g, ', ')})`;
|
||||||
|
} catch {
|
||||||
|
// lsof unavailable or no match
|
||||||
|
}
|
||||||
|
console.error(`Port ${port} (${protocol}) is already in use${detail}. Check with: lsof -i :${port}`);
|
||||||
|
} else {
|
||||||
|
console.error(`${protocol} server error:`, err.message);
|
||||||
|
}
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.listen(port);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function startApi() {
|
export async function startApi() {
|
||||||
if (server) {
|
if (server || httpsServer) {
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
if (starting) {
|
if (starting) {
|
||||||
@@ -109,31 +139,22 @@ export async function startApi() {
|
|||||||
startedAt = new Date().toISOString();
|
startedAt = new Date().toISOString();
|
||||||
const app = await createApi();
|
const app = await createApi();
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
const httpInstance = http.createServer(app);
|
||||||
const instance = app.listen(env.port);
|
await listenServer(httpInstance, env.port, 'HTTP');
|
||||||
|
server = httpInstance;
|
||||||
|
console.log(`API listening on port ${env.port} (HTTP)`);
|
||||||
|
|
||||||
instance.once('listening', () => {
|
if (env.httpsPort) {
|
||||||
server = instance;
|
try {
|
||||||
console.log(`API listening on port ${env.port}`);
|
const tls = await getOrCreateCert();
|
||||||
resolve();
|
const httpsInstance = https.createServer(tls, app);
|
||||||
});
|
await listenServer(httpsInstance, env.httpsPort, 'HTTPS');
|
||||||
|
httpsServer = httpsInstance;
|
||||||
instance.once('error', (err) => {
|
console.log(`API listening on port ${env.httpsPort} (HTTPS self-signed)`);
|
||||||
if (err.code === 'EADDRINUSE') {
|
} catch (err) {
|
||||||
let detail = '';
|
console.error('Failed to start HTTPS server:', err.message);
|
||||||
try {
|
}
|
||||||
const pids = execSync(`lsof -ti :${env.port}`, { encoding: 'utf8' }).trim();
|
}
|
||||||
if (pids) detail = ` (PID ${pids.replace(/\n/g, ', ')})`;
|
|
||||||
} catch {
|
|
||||||
// lsof unavailable or no match
|
|
||||||
}
|
|
||||||
console.error(`Port ${env.port} is already in use${detail}. Check with: lsof -i :${env.port}`);
|
|
||||||
} else {
|
|
||||||
console.error('Server error:', err.message);
|
|
||||||
}
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
})();
|
})();
|
||||||
@@ -146,13 +167,26 @@ export async function startApi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function stopApi() {
|
export async function stopApi() {
|
||||||
if (!server) return;
|
if (!server && !httpsServer) return;
|
||||||
// Long-lived SSE (and keep-alive) sockets otherwise block server.close()
|
// Long-lived SSE (and keep-alive) sockets otherwise block server.close()
|
||||||
// forever — node --watch hangs on "Waiting for graceful termination...".
|
// forever — node --watch hangs on "Waiting for graceful termination...".
|
||||||
closeAllSse();
|
closeAllSse();
|
||||||
await new Promise((resolve, reject) => {
|
const closers = [];
|
||||||
server.close((err) => (err ? reject(err) : resolve()));
|
if (server) {
|
||||||
server.closeAllConnections();
|
const s = server;
|
||||||
});
|
server = undefined;
|
||||||
server = undefined;
|
closers.push(new Promise((resolve, reject) => {
|
||||||
|
s.close((err) => (err ? reject(err) : resolve()));
|
||||||
|
s.closeAllConnections();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (httpsServer) {
|
||||||
|
const s = httpsServer;
|
||||||
|
httpsServer = undefined;
|
||||||
|
closers.push(new Promise((resolve, reject) => {
|
||||||
|
s.close((err) => (err ? reject(err) : resolve()));
|
||||||
|
s.closeAllConnections();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Promise.all(closers);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,18 @@ import { fileURLToPath } from 'url';
|
|||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
dotenv.config({ path: resolve(__dirname, '../../.env'), quiet: true });
|
dotenv.config({ path: resolve(__dirname, '../../.env'), quiet: true });
|
||||||
|
|
||||||
|
function parseHttpsPort(val) {
|
||||||
|
if (val === undefined || val === '') return 8443;
|
||||||
|
if (val === 'false' || val === '0' || val === 'none' || val === 'disabled') return null;
|
||||||
|
const n = Number(val);
|
||||||
|
return Number.isInteger(n) && n > 0 ? n : 8443;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
port: Number(process.env.PORT) || 4208,
|
port: Number(process.env.PORT) || 4208,
|
||||||
|
httpsPort: parseHttpsPort(process.env.HTTPS_PORT),
|
||||||
|
httpsKeyPath: process.env.HTTPS_KEY_PATH || resolve(__dirname, '../../certs/key.pem'),
|
||||||
|
httpsCertPath: process.env.HTTPS_CERT_PATH || resolve(__dirname, '../../certs/cert.pem'),
|
||||||
bodyLimit: process.env.BODY_LIMIT || '100mb',
|
bodyLimit: process.env.BODY_LIMIT || '100mb',
|
||||||
corsOrigins: (process.env.CORS_ORIGINS || 'http://localhost:5173')
|
corsOrigins: (process.env.CORS_ORIGINS || 'http://localhost:5173')
|
||||||
.split(',')
|
.split(',')
|
||||||
|
|||||||
71
src/util/cert.js
Normal file
71
src/util/cert.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
import selfsigned from 'selfsigned';
|
||||||
|
import env from '../config/env.js';
|
||||||
|
|
||||||
|
function getLocalIPv4() {
|
||||||
|
const addresses = [];
|
||||||
|
for (const iface of Object.values(os.networkInterfaces())) {
|
||||||
|
if (!iface) continue;
|
||||||
|
for (const addr of iface) {
|
||||||
|
if (addr.family === 'IPv4' && !addr.internal) {
|
||||||
|
addresses.push(addr.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads existing TLS credentials or generates a self-signed certificate.
|
||||||
|
* @returns {Promise<{ key: Buffer | string, cert: Buffer | string }>}
|
||||||
|
*/
|
||||||
|
export async function getOrCreateCert() {
|
||||||
|
const keyPath = env.httpsKeyPath;
|
||||||
|
const certPath = env.httpsCertPath;
|
||||||
|
|
||||||
|
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||||
|
return {
|
||||||
|
key: fs.readFileSync(keyPath),
|
||||||
|
cert: fs.readFileSync(certPath),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyDir = path.dirname(keyPath);
|
||||||
|
if (!fs.existsSync(keyDir)) {
|
||||||
|
fs.mkdirSync(keyDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const certDir = path.dirname(certPath);
|
||||||
|
if (!fs.existsSync(certDir)) {
|
||||||
|
fs.mkdirSync(certDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const altNames = [
|
||||||
|
{ type: 2, value: 'localhost' },
|
||||||
|
{ type: 7, ip: '127.0.0.1' },
|
||||||
|
];
|
||||||
|
for (const ip of getLocalIPv4()) {
|
||||||
|
altNames.push({ type: 7, ip });
|
||||||
|
}
|
||||||
|
|
||||||
|
const pems = await selfsigned.generate(
|
||||||
|
[{ name: 'commonName', value: 'localhost' }],
|
||||||
|
{
|
||||||
|
days: 365,
|
||||||
|
algorithm: 'sha256',
|
||||||
|
extensions: [{ name: 'subjectAltName', altNames }],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(keyPath, pems.private, { mode: 0o600 });
|
||||||
|
fs.writeFileSync(certPath, pems.cert);
|
||||||
|
|
||||||
|
console.log(`[tls] Generated self-signed certificate at ${certPath}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: pems.private,
|
||||||
|
cert: pems.cert,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user