Changes, so many changes

This commit is contained in:
Donavon McDowell 2024-12-13 17:04:15 -07:00
parent 5586c740bf
commit 1af235296c
32 changed files with 1298 additions and 76 deletions

View File

@ -1,5 +1,7 @@
MONGODB_URI='mongodb://localhost:27017'
MONGODB_NAME='docucenter'
NODE_ENV='development' #If set to production it will require https to function
JWT_SECRET='IjkAORBx^cLYpSb6Btz@te&vAJ3Pt*$z'
AUTH_MICROSOFT_ENTRA_ID_ID='3cdfac60-e7fb-4648-89d3-67966c497d35'
AUTH_MICROSOFT_ENTRA_ID_SECRET='5Gi8Q~_pmDtvN3.Jwqt85kiI.uiyAAC7Z.4iFayY'
AUTH_MICROSOFT_ENTRA_ID_ISSUER='538b9b1c-23fa-4102-b36e-a4d83fc9c4c1'

View File

@ -22,7 +22,6 @@ app.use(express.urlencoded({ limit: '100mb', extended: true })); // For URL-enco
const allowedOrigins = [
'https://docucenter.mpe.ca', // Production frontend
'http://localhost:5173', // Development frontend
];
const corsOptions = {

View File

@ -10,13 +10,17 @@
"license": "ISC",
"dependencies": {
"@azure/msal-node": "^2.16.2",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"mariadb": "^3.4.0",
"mongodb": "^6.12.0",
"mongoose": "^8.9.0",
"nodemailer": "^6.9.16",
"passport": "^0.7.0",
"passport-azure-ad": "^4.3.5",
"redis": "^4.7.0"
@ -45,6 +49,60 @@
"node": ">=16"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
@ -138,6 +196,11 @@
"@types/webidl-conversions": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -182,6 +245,32 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -195,8 +284,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"optional": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
@ -225,6 +313,19 @@
"node": ">=6.0.0"
}
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"node-addon-api": "^5.0.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@ -252,7 +353,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -375,6 +475,14 @@
"node": ">= 0.4"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@ -384,11 +492,23 @@
"node": ">=0.10.0"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"optional": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/content-disposition": {
"version": "0.5.4",
@ -417,6 +537,26 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": {
"cookie": "0.7.2",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -458,6 +598,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@ -483,6 +628,14 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": {
"node": ">=8"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@ -534,6 +687,11 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@ -685,6 +843,33 @@
"node": ">= 0.6"
}
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -693,6 +878,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/generic-pool": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
@ -773,6 +978,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@ -867,7 +1077,6 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@ -886,6 +1095,14 @@
"node": ">= 0.10"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@ -935,6 +1152,14 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -997,6 +1222,28 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/mariadb": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.0.tgz",
@ -1086,7 +1333,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -1103,6 +1349,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@ -1178,6 +1455,72 @@
"whatwg-url": "^13.0.0"
}
},
"node_modules/mongoose": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.0.tgz",
"integrity": "sha512-b58zY3PLNBcoz6ZXFckr0leJcVVBMAOBvD+7Bj2ZjghAwntXmNnqwlDixTKQU3UYoQIGTv+AQx/0ThsvaeVrCA==",
"dependencies": {
"bson": "^6.10.1",
"kareem": "2.6.3",
"mongodb": "~6.12.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/mquery/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/mquery/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -1220,6 +1563,49 @@
"node": ">= 0.6"
}
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/node-fetch/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/node-fetch/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@ -1256,6 +1642,40 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/nodemailer": {
"version": "6.9.16",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
@ -1303,7 +1723,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"optional": true,
"dependencies": {
"wrappy": "1"
}
@ -1389,7 +1808,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@ -1476,6 +1894,19 @@
"node": ">= 0.8"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/redis": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz",
@ -1598,6 +2029,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@ -1636,6 +2072,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sift": {
"version": "17.1.3",
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@ -1652,6 +2098,65 @@
"node": ">= 0.8"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1707,6 +2212,11 @@
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@ -1757,11 +2267,18 @@
"node": ">=16"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"optional": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",

View File

@ -11,13 +11,17 @@
"description": "",
"dependencies": {
"@azure/msal-node": "^2.16.2",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"mariadb": "^3.4.0",
"mongodb": "^6.12.0",
"mongoose": "^8.9.0",
"nodemailer": "^6.9.16",
"passport": "^0.7.0",
"passport-azure-ad": "^4.3.5",
"redis": "^4.7.0"

51
backend/server.js Normal file
View File

@ -0,0 +1,51 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const connectDB = require('./src/utils/database');
//Import routes
const userRoutes = require('./src/routes/userRoutes');
const authRoutes = require('./src/routes/authRoutes');
const documentRoutes = require('./src/routes/documentRoutes');
const app = express();
app.use(express.urlencoded({ extended: true })); // Parses form data
app.use(express.json()); // Parses JSON data
// Configure CORS to allow specific origins
const allowedOrigins = [
'https://docucenter.mpe.ca', // Production frontend
'http://localhost:5173', // Development frontend
];
const corsOptions = {
origin: (origin, callback) => {
// Allow requests with no origin (e.g., mobile apps or curl)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'], // Specify allowed HTTP methods
allowedHeaders: ['Content-Type', 'Authorization'], // Specify allowed headers
credentials: true, // Enable cookies and credentials sharing
};
app.use(cors(corsOptions));
// Connect to database
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/user', userRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/document', documentRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

View File

@ -0,0 +1,76 @@
const bcrypt = require('bcrypt'); // For password comparison
const jwt = require('jsonwebtoken'); // For generating JWTs
const User = require('../models/User');
const Email = require('../services/Email');
const Logger = require('../utils/logger');
exports.login = async (req, res) => {
const { email, password } = req.body;
try {
// Find the user by email
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Compare the provided password with the stored hashed password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).json({ message: 'Invalid email or password' });
}
// Generate a JWT for the authenticated user
const token = jwt.sign(
{ id: user._id, email: user.email }, // Payload
process.env.JWT_SECRET, // Secret key
{ expiresIn: '1h' } // Token expiration
);
// Check if the token was generated successfully
if (!token) {
return res.status(500).json({ message: 'Error generating authentication token' });
}
// Set the token as an HttpOnly cookie
res.cookie('authToken', token, {
httpOnly: true, // Cannot be accessed by JavaScript
secure: process.env.NODE_ENV === 'production', // Use Secure flag only in production (requires HTTPS)
sameSite: 'Strict', // Prevents CSRF attacks
maxAge: 3600000, // 1 hour
});
// Send a success response without including the token in the body
return res.status(200).json({ message: 'Login successful' });
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: error.message });
}
};
exports.forgotPassword = async (req, res) => {
const { email } = req.body;
try {
// Validate email and fetch user (omitted for brevity)
await Email.send(email, 'Test', 'Testttt');
Logger.log('email', 'info', 'An email was sent to' + email, 'authController.js');
res.status(200).json({ message: 'Password reset email sent!' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.resetPassword = async (req, res) => {
const { email, password } = req.body;
};
exports.getStatus = async (req, res) => {
res.status(200).json({ authenticated: true, user: req.user });
};

View File

@ -0,0 +1,69 @@
const Document = require('../models/Document');
exports.createDocument = async (req, res) => {
try {
const document = new Document(req.body);
await document.save();
res.status(201).json(document);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getDocument = async (req, res) => {
try {
const documents = await Document.find();
res.json(documents);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getDocumentById = async (req, res) => {
try {
const documentId = req.params.id;
const document = await Document.findById(documentId);
res.json(document);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateDocumentById = async (req, res) => {
try {
const documentId = req.params.id;
const updates = req.body;
const updatedDocument = await Document.findByIdAndUpdate(
documentId,
updates,
{ new: true, runValidators: true }
);
if (!updatedDocument) {
return res.status(404).json({ message: 'Document not found' });
}
res.json(updatedDocument);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.deleteDocumentById = async (req, res) => {
try {
const documentId = req.params.id;
const deletedDocument = await Document.findByIdAndDelete(documentId);
if (!deletedDocument) {
return res.status(404).json({ message: 'Document not found' });
}
res.json({ message: 'Document deleted successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};

View File

@ -0,0 +1,70 @@
const User = require('../models/User');
exports.createUser = async (req, res) => {
try {
const user = new User(req.body);
await user.save();
// Redirect to the login page
res.status(201).json(user);
} catch (error) {
res.status(400).json({ message: error.message });
}
};
exports.getUsers = async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.getUserById = async (req, res) => {
try {
const userId = req.params.id; // Get the ID from the request parameters
const user = await User.findById(userId);
res.json(user);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
exports.updateUserById = async (req, res) => {
try {
const userId = req.params.id; // Extract user ID from request parameters
const updates = req.body; // Data to update from request body
const updatedUser = await User.findByIdAndUpdate(
userId,
updates,
{ new: true, runValidators: true } // Options: return updated document and run validators
);
if (!updatedUser) {
return res.status(404).json({ message: 'User not found' });
}
res.json(updatedUser); // Send the updated user data
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any server errors
}
};
exports.deleteUserById = async (req, res) => {
try {
const userId = req.params.id; // Extract user ID from request parameters
const deletedUser = await User.findByIdAndDelete(userId); // Delete the user by ID
if (!deletedUser) {
return res.status(404).json({ message: 'User not found' });
}
res.json({ message: 'User deleted successfully' }); // Send success message
} catch (error) {
res.status(500).json({ message: error.message }); // Handle any server errors
}
};

View File

@ -0,0 +1,23 @@
const jwt = require('jsonwebtoken');
const authenticateToken = (req, res, next) => {
// Retrieve the token from the Authorization header
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1]; // Extract the token part after "Bearer"
if (!token) {
return res.status(401).json({ message: 'Access denied. No token provided.' });
}
try {
// Verify the token
const decoded = jwt.verify(token, process.env.JWT_SECRET); // Use your secret key
req.user = decoded; // Attach the decoded token payload to the request object
next(); // Proceed to the next middleware or route handler
} catch (error) {
res.status(403).json({ message: 'Invalid or expired token.' });
}
};
module.exports = authenticateToken;

View File

@ -0,0 +1,31 @@
const mongoose = require('mongoose');
const DocumentSchema = new mongoose.Schema(
{
title: { //Create, Delete, Update, Get
type: String,
required: true,
},
body: {
type: String,
required: true,
},
visibility: {
type: String,
required: true,
default: "Public",
},
tags: {
type: [String], //Array of strings
required: false,
default: [],
},
created_by: {
type: String,
required: false,
},
},
{ timestamps: true } // Adds createdAt and updatedAt automatically
);
module.exports = mongoose.model('Document', DocumentSchema);

25
backend/src/models/Log.js Normal file
View File

@ -0,0 +1,25 @@
const mongoose = require('mongoose');
const LogSchema = new mongoose.Schema(
{
event: { //Create, Delete, Update, Get
type: String,
required: false,
},
severity: { //info, warn, error, debug, critical
type: Number,
required: true,
},
message: { //What happened / extra details
type: String,
required: false,
},
source: { //What module the log came from
type: String,
required: false,
},
},
{ timestamps: true } // Adds createdAt and updatedAt automatically
);
module.exports = mongoose.model('Log', LogSchema);

View File

@ -0,0 +1,50 @@
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const UserSchema = new mongoose.Schema(
{
name: {
type: String,
required: false,
},
email: {
type: String,
required: true,
unique: true, // Ensures no duplicate emails
trim: true,
match: [/.+@.+\..+/, 'Please enter a valid email address'], // Simple email validation
},
password: {
type: String,
required: false, // Optional for OAuth users
},
role: {
type: String,
required: false, // Optional for OAuth users
default: "User", //User = Read, Write Permissions | Admin = Read, Write, Delete Permissions
},
oauthProviders: [
{
provider: {
type: String,
required: false,
enum: ['microsoft'], // Add other providers as needed
},
providerId: {
type: String,
required: false, // Unique ID from the OAuth provider
},
},
],
},
{ timestamps: true } // Adds createdAt and updatedAt automatically
);
// Hash the password before saving
UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
module.exports = mongoose.model('User', UserSchema);

View File

@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const authenticateToken = require('../middleware/authenticateToken');
router.get('/status', authenticateToken, authController.getStatus); //Check if the user is authenticated
router.post('/login', authController.login); //Check credentials
router.post('/forgot-password', authController.forgotPassword); //Forgot password
router.put('/reset-password', authController.resetPassword); //Update the password
module.exports = router;

View File

@ -0,0 +1,12 @@
const express = require('express');
const authenticateToken = require('../middleware/authenticateToken');
const router = express.Router();
const documentController = require('../controllers/documentController');
router.post('/', documentController.createDocument); //Create
router.get('/', documentController.getDocument); //Get all documents
router.get('/:id', documentController.getDocumentById); //Get a document by id
router.put('/:id', authenticateToken, documentController.updateDocumentById); //Update a document by id
router.delete('/:id', authenticateToken, documentController.deleteDocumentById); //Delete a document by id
module.exports = router;

View File

@ -0,0 +1,12 @@
const express = require('express');
const authenticateToken = require('../middleware/authenticateToken');
const router = express.Router();
const userController = require('../controllers/userController');
router.post('/', userController.createUser); //Create
router.get('/', authenticateToken, userController.getUsers); //Get all users
router.get('/:id', authenticateToken, userController.getUserById); //Get a user by id
router.put('/:id', authenticateToken, userController.updateUserById); //Update a user by id
router.delete('/:id', authenticateToken, userController.deleteUserById); //Delete a user by id
module.exports = router;

View File

@ -0,0 +1,21 @@
const nodemailer = require('nodemailer');
exports.send = async (to, subject, text) => {
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const mailOptions = {
from: process.env.EMAIL_FROM,
to,
subject,
text,
};
return transporter.sendMail(mailOptions);
};

View File

@ -0,0 +1,16 @@
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI+'/'+ process.env.MONGODB_NAME, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('MongoDB connected');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
module.exports = connectDB;

View File

@ -0,0 +1,11 @@
const Log = require('../models/Log');
exports.log = async (event, severity, message, source) => {
try {
const log = new Log({event: event, severity: severity, message: message, source: source});
await log.save();
} catch (error) {
}
};

View File

@ -1,13 +0,0 @@
import { SvelteKitAuth } from "@auth/sveltekit"
import Entra from "@auth/sveltekit/providers/microsoft-entra-id"
import { env } from "$env/dynamic/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Entra({
clientId: env.AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER,
}),
],
})

View File

@ -1 +0,0 @@
export { handle } from "./auth"

View File

@ -0,0 +1,5 @@
import { writable } from 'svelte/store';
export const isAuthenticated = writable(false);
export const name = writable(false);
export const errorMessage = writable('');

View File

@ -1,9 +1,42 @@
import type { LayoutServerLoad } from "./$types"
import type { LayoutServerLoad } from './$types';
import { PUBLIC_BASE_URL } from '$env/static/public';
export const load: LayoutServerLoad = async (event) => {
const session = await event.locals.auth()
export const load: LayoutServerLoad = async ({ cookies, fetch }) => {
let isAuthenticated = false;
let user = null;
let errorMessage = '';
return {
session,
// Retrieve the auth token from cookies
const token = cookies.get('authToken');
if (!token) {
errorMessage = 'No auth token found. Please log in.';
return { isAuthenticated, user, errorMessage };
}
}
try {
// Make a server-side fetch request to validate the token
const response = await fetch(`${PUBLIC_BASE_URL}/auth/status`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
isAuthenticated = data.authenticated;
user = data.user;
} else {
errorMessage = 'Authentication failed. Please log in again.';
}
} catch (error) {
console.error('Error checking authentication:', error);
errorMessage = 'An error occurred while checking authentication.';
}
// Return data to the layout
return { isAuthenticated, user, errorMessage };
};

View File

@ -1,9 +1,31 @@
<script lang="ts">
import { onMount } from 'svelte';
import { PUBLIC_BASE_URL } from '$env/static/public';
import '../app.postcss';
import { signIn, signOut } from "@auth/sveltekit/client"
import { page } from "$app/stores"
import { page } from "$app/stores";
import { isAuthenticated } from '$lib/stores/auth';
let dropDown = false;
let dropDown = false; // For the dropdown menu (login menu)
export let data: {
isAuthenticated: boolean;
errorMessage: string;
}
// Update the store values when this layout is loaded
$: isAuthenticated.set(data.isAuthenticated); //This is a store (MAGIC)
const handleSignOut = async () => {
const response = await fetch('/signout', {
method: 'POST',
});
if (response.ok) {
data.isAuthenticated = false;
} else {
console.error('Failed to sign out.');
}
};
</script>
@ -15,15 +37,13 @@
<div class="relative dropdown-container">
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
on:click={() => dropDown = !dropDown}>
{#if $page.data.session}
{#if $page.data.session.user?.image}
{#if data.isAuthenticated}
<img
src={$page.data.session.user.image}
src="https://www.citypng.com/public/uploads/preview/hd-mcdonalds-red-round-circular-circle-logo-icon-png-image-7017516947898359qtpcakiqi.png"
class="avatar rounded-full size-8"
alt="User Avatar"
/>
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
{/if}
{:else}
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
@ -33,23 +53,24 @@
{#if dropDown}
<!-- Dropdown menu -->
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
{#if $page.data.session}
{#if data.isAuthenticated}
<span class="signedInText block mb-2 text-sm">
<small>Signed in as</small><br/>
<strong>{$page.data.session.user?.name ?? "User"}</strong>
<small>Signed in</small><br/>
</span>
<button
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
on:click={() => { signOut(); dropDown = false;}}>
on:click={() => {dropDown = false; handleSignOut();}}>
Sign Out
</button>
{:else}
<span class="notSignedInText block mb-2">You are not signed in</span>
<a href="/login" on:click={() => {dropDown = false;}}>
<button
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
on:click={() => { signIn('microsoft-entra-id'); dropDown = false;}}>
>
Sign In
</button>
</a>
{/if}
</div>
{/if}

View File

@ -0,0 +1,59 @@
<script lang="ts">
import '../app.postcss';
import { signIn, signOut } from "@auth/sveltekit/client"
import { page } from "$app/stores"
let dropDown = false;
</script>
<header class="bg-blue-700 flex flex-row p-4 place-content-between items-center font-roboto shadow-xl">
<a href="/home">
<img class="max-w-36 mr-5 ml-4 aspect-auto" src="/branding/mpe_logo.png" alt="MPE Logo">
</a>
<div class="relative dropdown-container">
<button class="rounded-3xl bg-blue-500 p-2 flex flex-row justify-center items-center p-1 hover:bg-blue-400"
on:click={() => dropDown = !dropDown}>
{#if $page.data.session}
{#if $page.data.session.user?.image}
<img
src={$page.data.session.user.image}
class="avatar rounded-full size-8"
alt="User Avatar"
/>
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
{/if}
{:else}
<i class="fa-solid fa-right-to-bracket" style="color: #FFFFFF;"></i>
<i class="fa-solid fa-chevron-down ml-2 mr-2" style="color: #FFFFFF;"></i>
{/if}
</button>
{#if dropDown}
<!-- Dropdown menu -->
<div class="absolute right-0 mt-2 bg-white shadow-lg rounded-md p-4 z-50 text-nowrap">
{#if $page.data.session}
<span class="signedInText block mb-2 text-sm">
<small>Signed in as</small><br/>
<strong>{$page.data.session.user?.name ?? "User"}</strong>
</span>
<button
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
on:click={() => { signOut(); dropDown = false;}}>
Sign Out
</button>
{:else}
<span class="notSignedInText block mb-2">You are not signed in</span>
<button
class="bg-blue-500 text-white px-4 py-2 rounded-3xl hover:bg-blue-400 text-nowrap w-full"
on:click={() => { signIn('microsoft-entra-id'); dropDown = false;}}>
Sign In
</button>
{/if}
</div>
{/if}
</div>
</header>
<slot />

View File

@ -13,7 +13,7 @@
let title = "";
async function save() {
const url = PUBLIC_BASE_URL+"/documents";
const url = PUBLIC_BASE_URL+"/document";
let created_by = $page.data.session.user?.name ?? "User";

View File

@ -1,7 +1,9 @@
<script lang="js">
import { page } from "$app/stores"
import { onMount } from 'svelte';
import { PUBLIC_BASE_URL } from '$env/static/public';
import { isAuthenticated } from '$lib/stores/auth';
let document = null; // To store fetched document data
let error = null; // To store any error that occurs during fetch
@ -19,7 +21,7 @@
try {
// Make the GET request to your API
const response = await fetch(PUBLIC_BASE_URL+`/documents/${id}`);
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
if (response.ok) {
document = await response.json(); // Store document data
} else {
@ -69,7 +71,6 @@ async function deleteDocument(id) {
}
const result = await response.json();
console.log(result.message);
// Redirect after successful deletion
window.location.href = "/home";
@ -124,7 +125,7 @@ async function deleteDocument(id) {
<div class="flex flex-col justify-between">
<div class="w-full flex flex-row justify-between">
<h1 class="text-2xl">{document.title}</h1>
{#if $page.data.session}
{#if $isAuthenticated}
<div>
<a href="/edit?id={id}"><button class="bg-yellow-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto"><i class="fa-solid fa-pencil fa-lg"></i></button></a>
<button class="bg-red-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 text-white font-roboto" on:click={() => { deleteDocument(id)}}><i class="fa-solid fa-trash fa-lg"></i></button>

View File

@ -21,7 +21,7 @@
// Function to fetch the document by ID
async function fetchDocumentById(id) {
try {
const response = await fetch(PUBLIC_BASE_URL+`/documents/${id}`);
const response = await fetch(PUBLIC_BASE_URL+`/document/${id}`);
if (response.ok) {
const fetchedDocument = await response.json();
document = fetchedDocument; // Update document data
@ -57,7 +57,7 @@
async function save() {
const urlParams = new URLSearchParams(window.location.search);
const id = urlParams.get('id'); // Get the `id` from the query string
const url = PUBLIC_BASE_URL+"/documents/"+id;
const url = PUBLIC_BASE_URL+"/document/"+id;
console.log("Title value:", title);

View File

@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { page } from "$app/stores"
import { PUBLIC_BASE_URL } from '$env/static/public';
import { isAuthenticated } from '$lib/stores/auth';
let query = false;
let searchQuery = "";
@ -41,7 +42,7 @@
// Function to fetch all documents from the API
async function getAllDocuments() {
try {
const res = await fetch(PUBLIC_BASE_URL+`/documents`);
const res = await fetch(PUBLIC_BASE_URL+`/document`);
if (!res.ok) {
throw new Error(`Failed to fetch documents: ${res.statusText}`);
@ -71,14 +72,16 @@
documents = await getAllDocuments();
console.log("Fetched documents:", documents);
});
let message = "Hello";
</script>
<svelte:head>
<title>Home</title>
<title>DocuCenter</title>
</svelte:head>
<div class="flex items-center justify-center h-full flex-col">
{#if $page.data.session}
{#if $isAuthenticated}
<div class="flex flex-row justify-end w-full">
<a href="/add" class="bg-green-500 hover:bg-blue-400 border-double border-blue-100 border-2 p-3 rounded-full px-4 mr-4 mt-3 text-white font-roboto">
<i class="fa-solid fa-folder-plus fa-lg"></i>
@ -140,6 +143,5 @@
<p class="text-center text-gray-500">No results found.</p>
{/if}
</div>
</div>

View File

@ -1,14 +1,49 @@
<script>
import { signIn, signOut } from "@auth/sveltekit/client"
import { page } from "$app/stores"
import { PUBLIC_BASE_URL } from '$env/static/public';
async function handleSignIn(event) {
event.preventDefault(); // Prevent the default form submission
const form = event.target;
const formData = new FormData(form);
// Convert FormData to a JSON object
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch(`${PUBLIC_BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include', // Important to send and receive cookies
});
if (response.ok) {
const { token } = await response.json();
sessionStorage.setItem('authToken', token); // Store the token
// Redirect to the login page after success
window.location.href = '/home';
} else {
const error = await response.json();
alert(error.message);
}
} catch (err) {
console.error(err);
alert('An error occurred while signing up.');
}
}
</script>
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Sign in to your account</h2>
</div>
<div class="mt-4 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" action="#" method="POST">
<form class="space-y-6" on:submit={handleSignIn}>
<div>
<label for="email" class="block text-sm/6 font-medium text-gray-900">Email address</label>
<div class="mt-2">
@ -35,7 +70,7 @@
<p class="mt-10 text-center text-sm/6 text-gray-500">
Not a member?
<a href="#" class="font-semibold text-blue-500 hover:text-blue-400">Sign Up Now</a>
<a href="/signup" class="font-semibold text-blue-500 hover:text-blue-400">Sign Up Now</a>
</p>
</div>
</div>

View File

@ -1,3 +0,0 @@
import { signOut } from "../../auth"
import type { Actions } from "./$types"
export const actions: Actions = { default: signOut }

View File

@ -0,0 +1,12 @@
import type { RequestHandler } from '@sveltejs/kit';
export const POST: RequestHandler = async ({ cookies }) => {
try {
// Clear the authToken cookie
cookies.delete('authToken', { path: '/' });
return new Response(JSON.stringify({ success: true }), { status: 200 });
} catch (error) {
console.error('Error during sign out:', error);
return new Response(JSON.stringify({ success: false, message: 'Failed to sign out.' }), { status: 500 });
}
};

View File

@ -0,0 +1,71 @@
<script>
import { PUBLIC_BASE_URL } from '$env/static/public';
async function handleSignup(event) {
event.preventDefault(); // Prevent the default form submission
const form = event.target;
const formData = new FormData(form);
// Convert FormData to a JSON object
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch(`${PUBLIC_BASE_URL}/user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
// Redirect to the login page after success
window.location.href = '/login';
} else {
const error = await response.json();
alert(error.message);
}
} catch (err) {
console.error(err);
alert('An error occurred while signing up.');
}
}
</script>
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Sign up</h2>
</div>
<div class="mt-4 sm:mx-auto sm:w-full sm:max-w-sm">
<form class="space-y-6" on:submit={handleSignup}>
<div>
<label for="email" class="block text-sm/6 font-medium text-gray-900">Name</label>
<div class="mt-2">
<input type="text" name="name" id="name" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-500 sm:text-sm/6">
</div>
</div>
<div>
<label for="email" class="block text-sm/6 font-medium text-gray-900">Email address *</label>
<div class="mt-2">
<input type="email" name="email" id="email" autocomplete="email" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-500 sm:text-sm/6">
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="password" class="block text-sm/6 font-medium text-gray-900">Password *</label>
</div>
<div class="mt-2">
<input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-500 sm:text-sm/6">
</div>
</div>
<div>
<button type="submit" class="flex w-full justify-center rounded-md bg-blue-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">Create account</button>
</div>
</form>
</div>
</div>