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 @@ diff --git a/frontend/app/assets/css/colors.scss b/frontend/app/assets/css/colors.scss index d7126d6..ed979d4 100644 --- a/frontend/app/assets/css/colors.scss +++ b/frontend/app/assets/css/colors.scss @@ -3,27 +3,53 @@ $themes: ( dark: ( background: #141414, + background-light: #202020, background-line: #202324, background-fore: #10141f, + + window-handle-background: #191919, + window-background: #141414, + window-border: #202324, + window-shadow: #00000077, + + button-background: #20202077, + button-hover: #202020aa, + button-active: #202020cc, + hover: #21262d, selected: #4a4a4b, border-color: #819796, border: #202324, text: #ebede9, container-shadow: #151d28, - sticky-header-bg: #20202077 + sticky-header-bg: #20202077, + + icon-invert: 100% ), light: ( background: #ffffff, + background-light: #f9f9f9, background-line: #f0f0f0, background-fore: #ffffff, + + window-handle-background: #f0f0f0, + window-background: #ffffff, + window-border: #e0e0e0, + window-shadow: #d4d4d4, + + button-background: #f0f0f0, + button-hover: #e9e9e9, + button-active: #d4d4d4, + border-color: #e0e0e0, border: #f0f0f0, hover: #e9e9e9, selected: #d4d4d4, text: #1e1e1e, container-shadow: #5f6774, - sticky-header-bg: #fff + sticky-header-bg: #fff, + + icon-invert: 0% ) ); diff --git a/frontend/app/assets/css/main.scss b/frontend/app/assets/css/main.scss index 869d22a..1340187 100644 --- a/frontend/app/assets/css/main.scss +++ b/frontend/app/assets/css/main.scss @@ -1,25 +1,31 @@ body { - color: var(--text-color); + color: var(--color-text); font-family: "BookInsanityRemake", Arial, Helvetica, sans-serif; } body { - background-color: var(--background-color); + background-color: var(--color-background); margin: 0; } * { - color: var(--text-color); + color: var(--color-text); } a { - color: var(--link-color); + color: var(--color-link); } .icon { height: 12px; + filter: invert(var(--color-icon-invert)); } +* { + font-family: BookInsanityRemake; +} + + *::-webkit-scrollbar { width: 6px; @@ -39,6 +45,141 @@ a { color: var(--error-link); } +.buttons-row { + width: 100%; + padding-right: 10px; + padding-left: 10px; + display: flex; + flex-direction: row; + justify-content: center; +} + +.button-row { + margin-left: 5px; + margin-right: 5px; + flex-grow: 1; +} + +.form-field { + padding-bottom: 10px; + display: flex; + align-items: left; + flex-direction: column; + justify-content: left; +} + +hr { + border: 0; + height: 1px; + width: 30%; + overflow: visible; + position: relative; + margin: 16px auto 16px auto; + background-color: var(--separator); +} + +hr:before { + content: ""; + display: inline-block; + width: 8px; + height: 8px; + background-color: var(--separator); + position: absolute; + transform: rotate(45deg); + top: -2.5px; + left: 50%; + margin: -1px 0 0 -1px; +} + +input[type=text], input[type=password], input[type=email] { + background-color: var(--color-background-softer); + border: none; + padding: 8px; + border-radius: 6px; + color: var(--color-text); + transition: 300ms background-color; + border: solid 1px var(--color-border); +} + + +textarea { + background-color: var(--color-background-softer); + padding: 12px; + color: var(--color-text); + border: none; +} + +input[type=text]:focus, input[type=password]:focus, input[type=email]:focus { + outline: none; + background-color: var(--color-background-softest); +} + +textarea:focus { + outline: none; +} + +button { + margin-top: 5px; + margin-bottom: 5px; + + padding: 14px; + font-size: 15px; + border-radius: 6px; + outline: none; + + border: solid 1px var(--color-border); + -webkit-box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25); + -moz-box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25); + box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25); + + transition: 300ms background-color; + background-color: var(--color-button-background); + color: var(--color-text); +} + +button:hover { + background-color: var(--color-button-hover); +} + +button:active { + background-color: var(--color-button-active); +} + +.render-image { + max-width: 600px; + margin-left: auto; + margin-right: auto; + display: block; +} + +.confirm-form-button { + margin-top: 15px; +} + +.parameters { + display: flex; + flex-direction: column; + width: 100%; + padding: 10px; +} + +.param-element { + width: 100%; + display: flex; + flex-direction: row; +} + +.param-text { + margin-right: auto; +} + +.param-value { + margin-left: auto; +} + +.centered { + text-align: center; +} .window-wrapper { display: flex; @@ -51,4 +192,166 @@ a { -webkit-box-shadow: 0px 0px 10px -2px var(--shadow-color); -moz-box-shadow: 0px 0px 10px -2px var(--shadow-color); box-shadow: 0px 0px 10px -2px var(--shadow-color); -} \ No newline at end of file +} + +.document { + text-align: left; + width: 100%; +} + +.document.centered { + text-align: center; + justify-content: center; +} + +.document.item { + text-align: center; + width: 220px; +} + +.document.item img { + width: 64px; + height: 64px; +} + + +.document h1 { + font-weight: normal; + font-size: 32px; +} + +.document b { + font-weight: bold; +} + +.text-icon { + height: 18px; + width: 18px; + margin-bottom: -4px; +} + +.invert { + filter: invert(0.9); +} + +.main-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.row { + width: 100%; + display: flex; + overflow-x: auto; + scrollbar-width: thin; +} + + +span.important { + font-family: NodestoCapsCondensed; + font-size: 24px; + line-height: 32px; +} + +span.common { + color: var(--color-common); +} +span.uncommon { + color: var(--color-uncommon); +} +span.rare { + color: var(--color-rare); +} +span.very-rare { + color: var(--color-very-rare); +} +span.legendary { + color: var(--color-legendary); +} +span.artifact { + color: var(--color-artifact); +} + +.form-container { + width: 100%; +} + +.form-element { + padding: 10px 0 10px 0; + margin: 0 10px 0 10px; + display: flex; + align-items: center; + border-bottom: 1px dashed var(--color-border); +} + +.form-element label { + flex-grow: 0; + margin-right: 6px; + margin-left: 6px; +} + +.form-element.centered { + justify-content: center; +} + +.grow { + flex-grow: 1; +} + + +.subsection.border:first-child { + border-left: none; +} + +.subsection.border { + border-left: 1px solid var(--color-border); +} + +.subsection { + margin-left: 5px; + margin-right: 5px; + height: 32px; + display: flex; + align-items: left; + justify-content: left; +} + +.subsection.left { + align-items: left; + justify-content: left; + +} + +.subsection.right { + align-items: right; + justify-content: right; + +} + +.subsection.center { + align-items: center; + justify-content: center; +} + +.window-enter-active, +.window-leave-active { + transition: all 0.15s ease; +} +.window-enter-from, +.window-leave-to { + opacity: 0; + transform: translateY(15px); +} + +.window-wrapper { + background-color: var(--window-background); + + /* backdrop-filter: blur(10px); */ + position: fixed; + + + display: flex; + flex-direction: column; +} diff --git a/frontend/app/components/managers/ContentManager.vue b/frontend/app/components/managers/ContentManager.vue new file mode 100644 index 0000000..2e87ed6 --- /dev/null +++ b/frontend/app/components/managers/ContentManager.vue @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/components/managers/ToastManager.vue b/frontend/app/components/managers/ToastManager.vue new file mode 100644 index 0000000..ebf40ac --- /dev/null +++ b/frontend/app/components/managers/ToastManager.vue @@ -0,0 +1,120 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/app/components/managers/WindowManager.vue b/frontend/app/components/managers/WindowManager.vue index 33c89fc..9535d6e 100644 --- a/frontend/app/components/managers/WindowManager.vue +++ b/frontend/app/components/managers/WindowManager.vue @@ -29,7 +29,7 @@ const windows = Windows(); } .window-wrapper { - background-color: var(--window-background); + background-color: var(--color-window-background); /* backdrop-filter: blur(10px); */ position: fixed; @@ -37,6 +37,15 @@ const windows = Windows(); display: flex; flex-direction: column; -} + align-items: center; + + border: solid 1px var(--color-window-border); + + /* opacity: 0; */ + user-select: none; + -webkit-box-shadow: 0px 0px 10px -2px var(--color-window-shadow); + -moz-box-shadow: 0px 0px 10px -2px var(--color-window-shadow); + box-shadow: 0px 0px 10px -2px var(--color-window-shadow); + } diff --git a/frontend/app/components/viewer/TopBar.vue b/frontend/app/components/viewer/TopBar.vue index 7fad6b3..94b0431 100644 --- a/frontend/app/components/viewer/TopBar.vue +++ b/frontend/app/components/viewer/TopBar.vue @@ -21,7 +21,7 @@ import TopSearchBar from './topbar/TopSearchBar.vue'; flex-shrink: 0; min-height: 40px; width: 100%; - background-color: var(--top-bar-background-color); + background-color: var(--color-background-light); display: flex; } diff --git a/frontend/app/components/viewer/content/NoteContainer.vue b/frontend/app/components/viewer/content/NoteContainer.vue index 7e6373f..f80da38 100644 --- a/frontend/app/components/viewer/content/NoteContainer.vue +++ b/frontend/app/components/viewer/content/NoteContainer.vue @@ -1,8 +1,7 @@ + + + + + + + + diff --git a/frontend/app/components/windows/LoginWindow.vue b/frontend/app/components/windows/LoginWindow.vue index b0bb904..a06f3f9 100644 --- a/frontend/app/components/windows/LoginWindow.vue +++ b/frontend/app/components/windows/LoginWindow.vue @@ -1,8 +1,19 @@ @@ -26,8 +68,30 @@ onMounted(() => { -
-

Hola

+
+ + + + Dragonroll logo + + +
+
+ + +
+
+ + +
+
+ +
+
+

You don't have an account? Register

+
+
+
@@ -35,10 +99,41 @@ onMounted(() => { diff --git a/frontend/app/components/windows/RegisterWindow.vue b/frontend/app/components/windows/RegisterWindow.vue new file mode 100644 index 0000000..192e4d1 --- /dev/null +++ b/frontend/app/components/windows/RegisterWindow.vue @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/frontend/app/components/windows/partials/WindowHandle.vue b/frontend/app/components/windows/partials/WindowHandle.vue index b6999aa..48e3d39 100644 --- a/frontend/app/components/windows/partials/WindowHandle.vue +++ b/frontend/app/components/windows/partials/WindowHandle.vue @@ -133,7 +133,7 @@ defineExpose({ display: flex; - background-color: var(--color-handler); + background-color: var(--color-window-handle-background); } diff --git a/frontend/app/composables/useTheme.ts b/frontend/app/composables/useTheme.ts new file mode 100644 index 0000000..7d5ea76 --- /dev/null +++ b/frontend/app/composables/useTheme.ts @@ -0,0 +1,47 @@ +import { ref, onMounted, watch } from 'vue' + +type Theme = 'light' | 'dark' +type Accent = 'katlum' + +const theme = ref('light') +const accent = ref('katlum') + +const applyTheme = () => { + document.documentElement.setAttribute('data-theme', theme.value) + document.documentElement.setAttribute('data-accent', accent.value) +} + +const setTheme = (value: Theme) => { + theme.value = value + localStorage.setItem('theme', value) + applyTheme(); +} + +const setAccent = (value: Accent) => { + accent.value = value + localStorage.setItem('accent', value) + applyTheme(); +} + +const setupTheme = () => { + const savedTheme = localStorage.getItem('theme') as Theme | null + const savedAccent = localStorage.getItem('accent') as Accent | null + + const media = window.matchMedia('(prefers-color-scheme: dark)') + + theme.value = savedTheme || (media.matches ? 'dark' : 'light') + accent.value = savedAccent || 'katlum' + + applyTheme() + + media.addEventListener('change', (e) => { + if (!localStorage.getItem('theme')) { + theme.value = e.matches ? 'dark' : 'light' + applyTheme() + } + }) +}; + +watch([theme, accent], applyTheme) + +export { theme, accent, setTheme, setAccent, setupTheme} \ No newline at end of file diff --git a/frontend/app/plugins/emitter.ts b/frontend/app/plugins/emitter.ts deleted file mode 100644 index 23efce3..0000000 --- a/frontend/app/plugins/emitter.ts +++ /dev/null @@ -1,11 +0,0 @@ -import mitt from 'mitt' - -export default defineNuxtPlugin(() => { - const emitter = mitt() - - return { - provide: { - emitter - } - } -}) \ No newline at end of file diff --git a/frontend/app/services/BackendURL.js b/frontend/app/services/BackendURL.js new file mode 100644 index 0000000..fbf9bcf --- /dev/null +++ b/frontend/app/services/BackendURL.js @@ -0,0 +1,11 @@ +var backendUrl = '' +if (import.meta.env.PROD) { + backendUrl = 'https://api.aranroig.com/'; +} else { + backendUrl = 'http://localhost:5000/' +} + + +export { + backendUrl +}; \ No newline at end of file diff --git a/frontend/app/services/Content.js b/frontend/app/services/Content.js new file mode 100644 index 0000000..a11a08a --- /dev/null +++ b/frontend/app/services/Content.js @@ -0,0 +1,12 @@ +import { ref } from 'vue'; + +const ShowContent = ref(false); + +function SetShowContent(value) { + ShowContent.value = value; +} + +export { + ShowContent, + SetShowContent +} \ No newline at end of file diff --git a/frontend/app/services/Emitter.js b/frontend/app/services/Emitter.js new file mode 100644 index 0000000..ca9d3e0 --- /dev/null +++ b/frontend/app/services/Emitter.js @@ -0,0 +1,3 @@ +import mitt from 'mitt' + +export const emitter = mitt(); \ No newline at end of file diff --git a/frontend/app/services/Server.js b/frontend/app/services/Server.js new file mode 100644 index 0000000..698422d --- /dev/null +++ b/frontend/app/services/Server.js @@ -0,0 +1,21 @@ +import axios from 'axios'; + +import { backendUrl } from './BackendURL'; + +const server = axios.create({ + baseURL: backendUrl, + headers: { + "Access-Control-Allow-Origin": "*", + } +}); + +// Attach token dynamically on each request via interceptor +server.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export default () => server; \ No newline at end of file diff --git a/frontend/app/services/Toaster.js b/frontend/app/services/Toaster.js new file mode 100644 index 0000000..4f32469 --- /dev/null +++ b/frontend/app/services/Toaster.js @@ -0,0 +1,9 @@ +import { emitter } from './Emitter'; + +function DisplayToast(color, text, duration = 1000){ + emitter.emit("toast", {color, text, duration}); +} + +export { + DisplayToast, +} \ No newline at end of file diff --git a/frontend/app/services/User.js b/frontend/app/services/User.js new file mode 100644 index 0000000..1ba3a39 --- /dev/null +++ b/frontend/app/services/User.js @@ -0,0 +1,80 @@ +import { ref } from 'vue'; +import Server from './Server'; + +const UserStatus = ref(0); + +function parseJwt(token) { + return JSON.parse(atob(token.split('.')[1])); +} + +function SetUser(token){ + localStorage.setItem('token', token); + UserStatus.value = 1; +} + +async function HasAdmin(){ + let response = await Server().get('/user/has-admin'); + return response.data.status != "init"; +} + +async function SetUserSetting(key, value){ + let user = GetUser(); + if(!user.settings) user.settings = {}; + user.settings[key] = value; + const response = await Server().post('/user/update-settings', { settings: user.settings }); + return response.data.settings; +} + +async function GetUserSetting(key){ + const response = await Server().get('/user/get-settings'); + if (response.data.settings) + return response.data.settings[key]; + return undefined; +} + +function GetUser(){ + const token = localStorage.getItem('token'); + + if(token){ + const data = parseJwt(token); + + // Check if token is expired + const now = Date.now() / 1000; + if(now > data.exp){ + LogoutUser(); + return undefined; + } + + return data; + } + return undefined; +} + +function IsAdmin(){ + const user = GetUser(); + if(user){ + return user.admin; + } +} + +function LoadUser(){ + const token = localStorage.getItem('token'); + if(token) UserStatus.value = 1; +} + +function LogoutUser(){ + localStorage.removeItem("token"); + UserStatus.value = 0; +} + +export { + UserStatus, + GetUser, + SetUser, + LoadUser, + IsAdmin, + LogoutUser, + HasAdmin, + GetUserSetting, + SetUserSetting +} \ No newline at end of file diff --git a/frontend/app/services/Windows.js b/frontend/app/services/Windows.js index 75c1374..fd45729 100644 --- a/frontend/app/services/Windows.js +++ b/frontend/app/services/Windows.js @@ -3,22 +3,29 @@ import { ref } from 'vue' const windows = ref([]); import LoginWindow from '~/components/windows/LoginWindow.vue'; +import RegisterWindow from '~/components/windows/RegisterWindow.vue'; +import ExampleWindow from '~/components/windows/ExampleWindow.vue'; let windowMap = { - login: LoginWindow + login: LoginWindow, + register: RegisterWindow, + example: ExampleWindow }; -async function InjectWindow(window_type, plugin, window_component) { - let systemWidows = {}; - systemWidows[window_type] = (await import(`../../plugins/${plugin}/views/${window_component}.vue`)).default; - windowMap = { ...windowMap, ...systemWidows }; -} - // Presets const defValues = { + 'example': { + id: "example", + title: "Example", + close: () => ClearWindow('example') + }, 'login': { id: 'login', title: 'Login', + }, + 'register': { + id: 'register', + title: 'Register' } } @@ -59,7 +66,9 @@ function SetupHandle(id, handle) { SetOnTop(id); }); + // Move window listeners handler.addEventListener("mousedown", (event) => { + if(win.noMove) return; draggingWindow = true; let windowRect = currentWindow.getBoundingClientRect(); @@ -67,8 +76,8 @@ function SetupHandle(id, handle) { offsetY = windowRect.top - event.clientY; }) - // Move window listeners document.addEventListener("mousemove", (event) => { + if(win.noMove) return; if (!draggingWindow) return; if (event.clientX + offsetX < -currentWindow.getBoundingClientRect().width + 20) currentWindow.style.left = (-currentWindow.getBoundingClientRect().width + 20) + "px"; @@ -81,6 +90,7 @@ function SetupHandle(id, handle) { }) document.addEventListener("mouseup", (event) => { + if(win.noMove) return; draggingWindow = false; // ummm suposo que no pots tancar mentres mous? SaveWindowPos({ id, x: parseInt(currentWindow.style.left, 10), y: parseInt(currentWindow.style.top, 10) }); @@ -126,6 +136,11 @@ function SetResizable(id, resizable) { win.resizable = resizable; } +function SetMovable(id, movable) { + let win = GetWindowWithId(id); + win.noMove = !movable; +} + function SetSize(id, size) { let currentWindowId = "window-wrapper-" + id; let currentWindow = document.getElementById(currentWindowId); @@ -284,10 +299,10 @@ export { SetMaxSize, SetMinSize, SetPosition, + SetMovable, ResetPosition, Windows, WindowMap, - InjectWindow, ReloadRef, ClearWindows, CreateWindow, diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index d763d19..0dce3b6 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -4,7 +4,8 @@ export default defineNuxtConfig({ optimizeDeps: { include: [ '@vue/devtools-core', - '@vue/devtools-kit' + '@vue/devtools-kit', + 'axios' ] } }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c898f04..06cf2c7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,7 @@ "name": "dragonroll", "hasInstallScript": true, "dependencies": { + "axios": "^1.15.2", "mitt": "^3.0.1", "nuxt": "^4.4.2", "pixelarticons": "^2.1.0", @@ -3991,6 +3992,12 @@ "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", @@ -4027,6 +4034,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/b4a": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", @@ -4345,6 +4363,19 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -4448,6 +4479,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4895,6 +4938,15 @@ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -5037,6 +5089,20 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -5103,6 +5169,15 @@ "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", "license": "MIT" }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -5118,6 +5193,33 @@ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", @@ -5359,6 +5461,26 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5375,6 +5497,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/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/form-data/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/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -5469,12 +5628,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-port-please": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", "license": "MIT" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -5560,6 +5756,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5604,6 +5812,33 @@ "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", "license": "MIT" }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -6268,6 +6503,15 @@ "source-map-js": "^1.2.1" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -7622,6 +7866,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", diff --git a/frontend/package.json b/frontend/package.json index 886d344..a59e3ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { + "axios": "^1.15.2", "mitt": "^3.0.1", "nuxt": "^4.4.2", "pixelarticons": "^2.1.0", diff --git a/frontend/public/img/logo-light.png b/frontend/public/img/logo-light.png new file mode 100644 index 0000000..55e2931 Binary files /dev/null and b/frontend/public/img/logo-light.png differ diff --git a/frontend/public/img/logo-splash-light.png b/frontend/public/img/logo-splash-light.png new file mode 100644 index 0000000..fe79878 Binary files /dev/null and b/frontend/public/img/logo-splash-light.png differ diff --git a/frontend/public/img/logo-splash.png b/frontend/public/img/logo-splash.png new file mode 100644 index 0000000..07f45c6 Binary files /dev/null and b/frontend/public/img/logo-splash.png differ diff --git a/frontend/public/img/logo.png b/frontend/public/img/logo.png new file mode 100644 index 0000000..5a2e59f Binary files /dev/null and b/frontend/public/img/logo.png differ