diff --git a/backend/.env b/backend/.env index 835f8ee..bc8fa04 100644 --- a/backend/.env +++ b/backend/.env @@ -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' \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index 509a1b7..0c4dc36 100644 --- a/backend/app.js +++ b/backend/app.js @@ -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 = { diff --git a/backend/package-lock.json b/backend/package-lock.json index a476d6f..f4aabea 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -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", diff --git a/backend/package.json b/backend/package.json index b07b23e..b25f52e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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" diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..3383bda --- /dev/null +++ b/backend/server.js @@ -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}`); +}); \ No newline at end of file diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js new file mode 100644 index 0000000..4fc9004 --- /dev/null +++ b/backend/src/controllers/authController.js @@ -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 }); +}; + diff --git a/backend/src/controllers/documentController.js b/backend/src/controllers/documentController.js new file mode 100644 index 0000000..0e0ef9d --- /dev/null +++ b/backend/src/controllers/documentController.js @@ -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 }); + } +}; diff --git a/backend/src/controllers/userController.js b/backend/src/controllers/userController.js new file mode 100644 index 0000000..62de111 --- /dev/null +++ b/backend/src/controllers/userController.js @@ -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 + } +}; diff --git a/backend/src/middleware/authenticateToken.js b/backend/src/middleware/authenticateToken.js new file mode 100644 index 0000000..8799a68 --- /dev/null +++ b/backend/src/middleware/authenticateToken.js @@ -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; diff --git a/backend/src/models/Document.js b/backend/src/models/Document.js new file mode 100644 index 0000000..e58541e --- /dev/null +++ b/backend/src/models/Document.js @@ -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); diff --git a/backend/src/models/Log.js b/backend/src/models/Log.js new file mode 100644 index 0000000..b4c7cd2 --- /dev/null +++ b/backend/src/models/Log.js @@ -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); diff --git a/backend/src/models/User.js b/backend/src/models/User.js new file mode 100644 index 0000000..fd74cb0 --- /dev/null +++ b/backend/src/models/User.js @@ -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); diff --git a/backend/src/routes/authRoutes.js b/backend/src/routes/authRoutes.js new file mode 100644 index 0000000..6acf732 --- /dev/null +++ b/backend/src/routes/authRoutes.js @@ -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; \ No newline at end of file diff --git a/backend/src/routes/documentRoutes.js b/backend/src/routes/documentRoutes.js new file mode 100644 index 0000000..4609175 --- /dev/null +++ b/backend/src/routes/documentRoutes.js @@ -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; \ No newline at end of file diff --git a/backend/src/routes/userRoutes.js b/backend/src/routes/userRoutes.js new file mode 100644 index 0000000..5705343 --- /dev/null +++ b/backend/src/routes/userRoutes.js @@ -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; \ No newline at end of file diff --git a/backend/src/services/Email.js b/backend/src/services/Email.js new file mode 100644 index 0000000..f256b7b --- /dev/null +++ b/backend/src/services/Email.js @@ -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); +}; + diff --git a/backend/src/utils/database.js b/backend/src/utils/database.js new file mode 100644 index 0000000..422901b --- /dev/null +++ b/backend/src/utils/database.js @@ -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; \ No newline at end of file diff --git a/backend/src/utils/logger.js b/backend/src/utils/logger.js new file mode 100644 index 0000000..6034e82 --- /dev/null +++ b/backend/src/utils/logger.js @@ -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) { + + } +}; + diff --git a/frontend/src/auth.ts b/frontend/src/auth.ts deleted file mode 100644 index f05f426..0000000 --- a/frontend/src/auth.ts +++ /dev/null @@ -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, - }), - ], -}) \ No newline at end of file diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts deleted file mode 100644 index 69f0f9e..0000000 --- a/frontend/src/hooks.server.ts +++ /dev/null @@ -1 +0,0 @@ -export { handle } from "./auth" diff --git a/frontend/src/lib/stores/auth.ts b/frontend/src/lib/stores/auth.ts new file mode 100644 index 0000000..906de94 --- /dev/null +++ b/frontend/src/lib/stores/auth.ts @@ -0,0 +1,5 @@ +import { writable } from 'svelte/store'; + +export const isAuthenticated = writable(false); +export const name = writable(false); +export const errorMessage = writable(''); diff --git a/frontend/src/routes/+layout.server.ts b/frontend/src/routes/+layout.server.ts index 5235aed..4c85c3f 100644 --- a/frontend/src/routes/+layout.server.ts +++ b/frontend/src/routes/+layout.server.ts @@ -1,9 +1,42 @@ -import type { LayoutServerLoad } from "./$types" - -export const load: LayoutServerLoad = async (event) => { - const session = await event.locals.auth() - - return { - session, +import type { LayoutServerLoad } from './$types'; +import { PUBLIC_BASE_URL } from '$env/static/public'; + +export const load: LayoutServerLoad = async ({ cookies, fetch }) => { + let isAuthenticated = false; + let user = null; + let errorMessage = ''; + + // 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 }; } -} \ No newline at end of file + + 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 }; +}; + diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 4c27f5e..d812d5b 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,9 +1,31 @@ @@ -15,15 +37,13 @@