diff --git a/backend/.env.production b/backend/.env.production
index 9f4a66c..b6de573 100644
--- a/backend/.env.production
+++ b/backend/.env.production
@@ -1,3 +1,3 @@
PORT=5000
-DB_URI=mongodb://10.1.1.7:27017/
+DB_URI=mongodb://10.1.1.7:27017/dragonroll
NODE_ENV=production
\ No newline at end of file
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 7fa3f9c..c736a2c 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -9,11 +9,17 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
+ "express-rate-limit": "^8.4.1",
+ "jsonwebtoken": "^9.0.3",
"mongoose": "^9.3.0",
- "nodemon": "^3.1.14"
+ "multer": "^2.1.1",
+ "nodemon": "^3.1.14",
+ "passport": "^0.7.0"
}
},
"node_modules/@mongodb-js/saslprep": {
@@ -66,6 +72,12 @@
"node": ">= 8"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
+ "license": "MIT"
+ },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -75,6 +87,15 @@
"node": "18 || 20 || >=22"
}
},
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -144,6 +165,29 @@
"node": ">=20.19.0"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -206,6 +250,21 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/concat-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+ "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+ "engines": [
+ "node >= 6.0"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.0.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/content-disposition": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
@@ -237,6 +296,25 @@
"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==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
@@ -315,6 +393,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -418,6 +505,24 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/express-rate-limit": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz",
+ "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "10.1.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -634,6 +739,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
+ "node_modules/ip-address": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
+ "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -691,6 +805,49 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/kareem": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz",
@@ -700,6 +857,48 @@
"node": ">=18.0.0"
}
},
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -880,6 +1079,68 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/multer": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
+ "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.6.0",
+ "concat-stream": "^2.0.0",
+ "type-is": "^1.6.18"
+ },
+ "engines": {
+ "node": ">= 10.16.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/multer/node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/multer/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/multer/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/multer/node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -977,6 +1238,32 @@
"node": ">= 0.8"
}
},
+ "node_modules/passport": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
+ "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "passport-strategy": "1.x.x",
+ "pause": "0.0.1",
+ "utils-merge": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jaredhanson"
+ }
+ },
+ "node_modules/passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/path-to-regexp": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
@@ -987,6 +1274,11 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
+ },
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
@@ -1066,6 +1358,20 @@
"node": ">= 0.10"
}
},
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1094,6 +1400,26 @@
"node": ">= 18"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -1271,6 +1597,23 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -1339,6 +1682,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "license": "MIT"
+ },
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -1354,6 +1703,21 @@
"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==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
diff --git a/backend/package.json b/backend/package.json
index 66339b2..8bd69b9 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -10,10 +10,16 @@
"dev": "nodemon src/index.js"
},
"dependencies": {
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
+ "express-rate-limit": "^8.4.1",
+ "jsonwebtoken": "^9.0.3",
"mongoose": "^9.3.0",
- "nodemon": "^3.1.14"
+ "multer": "^2.1.1",
+ "nodemon": "^3.1.14",
+ "passport": "^0.7.0"
}
}
diff --git a/backend/src/index.js b/backend/src/index.js
index adfa144..f15f5d4 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -1,5 +1,7 @@
const express = require("express");
const cors = require('cors');
+const cookieParser = require('cookie-parser');
+const passport = require('passport');
const dotenv = require('dotenv');
@@ -14,14 +16,39 @@ if(process.env.NODE_ENV) {
const app = express();
const connectDB = require("./db");
+// JSON LIMIT EXPRESS
+app.use(express.json({ limit: '50mb' }));
+app.use(express.urlencoded({
+ extended: true,
+ limit: '50mb'
+}));
+
// connect database
connectDB();
+// CORS
+app.use(cookieParser());
+
app.use(cors({
origin: 'http://localhost:3000',
credentials: true, // if using cookies/auth
}));
+// ROUTES (NO AUTH)
+app.use('/user', require('./routes/user'));
+
+// AUTH
+checkAuth = passport.authenticate('jwt', { session: false });
+app.use(checkAuth);
+
+// ROUTES WITH AUTH
+/*
+app.use('/campaign', require('./routes/campaign'));
+app.use('/maps', require('./routes/map'));
+app.use('/datagen', require('./routes/datagen'));
+app.use('/admin', require('./routes/admin'));
+*/
+
app.get("/api/test", (req, res) => {
console.log("Hey");
res.json({"message": "Hello from backend!"});
diff --git a/backend/src/models/User.js b/backend/src/models/User.js
new file mode 100644
index 0000000..46b5939
--- /dev/null
+++ b/backend/src/models/User.js
@@ -0,0 +1,16 @@
+const mongoose = require("mongoose");
+const Schema = mongoose.Schema;
+
+const UserSchema = new Schema({
+ name: {type: String, required: true},
+ username: { type: String, required: true, unique: true },
+ email: { type: String, required: true, unique: true },
+ password: { type: String },
+ date: { type: Date, default: Date.now},
+ admin: {type: Boolean, default: false},
+ image: { type: String },
+ setupCode: { type: String },
+ settings: { type: Object }
+});
+
+module.exports = mongoose.model('User', UserSchema);
\ No newline at end of file
diff --git a/backend/src/routes/user.js b/backend/src/routes/user.js
new file mode 100644
index 0000000..fabd5e2
--- /dev/null
+++ b/backend/src/routes/user.js
@@ -0,0 +1,175 @@
+const express = require('express')
+const router = express.Router();
+
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const passport = require('passport');
+const secret = require('../services/keys').secret;
+const rateLimitMiddleware = require("../services/rate-limiter");
+const crypto = require("crypto");
+
+const { isAdmin } = require('../services/middleware');
+
+const User = require("../models/User");
+
+const upload = require("../services/storage");
+
+
+
+// Admin registers new user
+router.post('/register', isAdmin, async (req, res) => {
+ try {
+ let setupCode = crypto.randomBytes(64).toString('base64url');
+
+ let user = new User({
+ admin: false,
+ name: crypto.randomBytes(16).toString('base64url'),
+ username: crypto.randomBytes(16).toString('base64url'),
+ email: crypto.randomBytes(16).toString('base64url'),
+ setupCode
+ });
+
+ await user.save();
+ res.json({ status: "ok", code: setupCode });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+// User gets if setup account exists given the query code
+router.get('/verify-setup', async (req, res) => {
+ try {
+ const user = await User.findOne({ setupCode: req.query.code });
+ if (user) {
+ res.json({ status: "ok", code: req.query.code });
+ } else {
+ res.json({ status: "error", msg: "not-exists" });
+ }
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+// User posts the parameters of his new account given by admin
+router.post('/setup', rateLimitMiddleware, async (req, res) => {
+ const { name, username, email, password } = req.body;
+ const setupCode = req.query.code;
+
+ if (!(name && username && email && password && setupCode)) {
+ return res.json({ status: "error", msg: "params" });
+ }
+
+ try {
+ const user = await User.findOne({ setupCode });
+ if (!user) {
+ return res.json({ status: "error", msg: "not-found" });
+ }
+
+ const sameUser = await User.findOne({ email });
+ if (sameUser) {
+ return res.json({ status: "error", msg: "already-email" });
+ }
+
+ const salt = await bcrypt.genSalt(10);
+ user.password = await bcrypt.hash(password, salt);
+ user.username = username;
+ user.email = email;
+ user.setupCode = undefined;
+
+ await user.save();
+ res.json({ status: "ok" });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+// Login post
+router.post('/login', rateLimitMiddleware, async (req, res) => {
+ const { username, password } = req.body;
+
+ if (!(username && password)) {
+ return res.json({ status: "error", msg: "params" });
+ }
+
+ try {
+ const user = await User.findOne({ username });
+ if (!user) {
+ return res.json({ status: "error", msg: "wrong" });
+ }
+
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) {
+ return res.json({ status: "error", msg: "wrong" });
+ }
+
+ const payload = {
+ _id: user._id,
+ username: user.username,
+ name: user.name,
+ email: user.email,
+ admin: user.admin,
+ settings: user.settings
+ };
+
+ const token = await new Promise((resolve, reject) => {
+ jwt.sign(payload, secret, { expiresIn: 172800 }, (err, token) => {
+ if (err) reject(err);
+ else resolve(token);
+ });
+ });
+
+ res.json({ status: "ok", token, msg: "success" });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+// Upload avatar post
+router.post("/upload-avatar", upload.single("image"), passport.authenticate('jwt', {session: false}), async (req, res) => {
+ try {
+ const imageName = req.file.filename;
+ await User.updateOne(req.user, { image: imageName });
+ res.json({ status: "ok", msg: "uploaded" });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+router.get("/retrieve-avatar", async (req, res) => {
+ try {
+ const data = await User.findOne({ username: req.query.username });
+ res.json({ status: "ok", image: data.image });
+ } catch (err) {
+ res.json({ status: "error" });
+ }
+});
+
+router.get("/has-admin", async (req, res) => {
+ try {
+ const data = await User.findOne({ admin: true });
+ if (data) res.json({ status: "ok" });
+ else res.json({ status: "init" });
+ } catch (err) {
+ res.json({ status: "error" });
+ }
+});
+
+router.post("/update-settings", passport.authenticate('jwt', {session: false}), async (req, res) => {
+ try {
+ await User.updateOne(req.user, { settings: req.body.settings });
+ res.json({ status: "ok", settings: req.body.settings });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+router.get('/get-settings', passport.authenticate('jwt', {session: false}), async (req, res) => {
+ try {
+ const data = await User.findOne(req.user);
+ res.json({ status: "ok", settings: data.settings });
+ } catch (err) {
+ res.json({ status: "error", msg: "internal" });
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/backend/src/services/keys.js b/backend/src/services/keys.js
new file mode 100644
index 0000000..1d603aa
--- /dev/null
+++ b/backend/src/services/keys.js
@@ -0,0 +1,5 @@
+const fs = require('fs');
+
+module.exports = {
+ secret: "putyoursecrethere"
+}
\ No newline at end of file
diff --git a/backend/src/services/middleware.js b/backend/src/services/middleware.js
new file mode 100644
index 0000000..000f925
--- /dev/null
+++ b/backend/src/services/middleware.js
@@ -0,0 +1,17 @@
+const User = require("../models/User");
+
+async function isAdmin(req, res, next) {
+ try {
+ const user = await User.findOne(req.user).lean();
+ if (user && user.admin) {
+ return next();
+ }
+ res.json({ status: "error", msg: "unauthorized" });
+ } catch (err) {
+ res.json({ status: "error", msg: err.message });
+ }
+}
+
+module.exports = {
+ isAdmin
+}
\ No newline at end of file
diff --git a/backend/src/services/rate-limiter.js b/backend/src/services/rate-limiter.js
new file mode 100644
index 0000000..963bddc
--- /dev/null
+++ b/backend/src/services/rate-limiter.js
@@ -0,0 +1,11 @@
+const setRateLimit = require("express-rate-limit");
+
+// Rate limit middleware
+const rateLimitMiddleware = setRateLimit({
+ windowMs: 60 * 60 * 1000,
+ max: 150,
+ message: "Has fet masses peticions de login en una hora (ets un robot???)",
+ headers: true,
+});
+
+module.exports = rateLimitMiddleware;
\ No newline at end of file
diff --git a/backend/src/services/storage.js b/backend/src/services/storage.js
new file mode 100644
index 0000000..1a2a074
--- /dev/null
+++ b/backend/src/services/storage.js
@@ -0,0 +1,14 @@
+const multer = require('multer');
+
+var storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ cb(null, 'uploads')
+ },
+ filename: function (req, file, cb) {
+ cb(null, file.fieldname + '-' + Date.now())
+ }
+});
+
+var upload = multer({storage: storage});
+module.exports = upload;
+
\ No newline at end of file
diff --git a/frontend/app/app.vue b/frontend/app/app.vue
index 0bd0a79..3d15c55 100644
--- a/frontend/app/app.vue
+++ b/frontend/app/app.vue
@@ -1,9 +1,7 @@
Hola
+