From 7878081beec73546799d2399194234d268be4a50 Mon Sep 17 00:00:00 2001 From: Praneel Pothukanuri Date: Wed, 26 Aug 2026 04:48:54 +0000 Subject: [PATCH] Security hardening: remove committed session tokens, fix open redirect and debug server, validate input --- .env.example | 16 + .gitignore | 4 + UI_Front/package-lock.json | 671 ++++++++++++------ backend/app.py | 7 +- backend/config.py | 68 +- backend/routes/auth.py | 96 +-- backend/routes/playlist.py | 193 +++-- backend/services/spotify.py | 16 +- .../15c846f2599d1bfc08cc5cb4a5ed974f | Bin 720 -> 0 bytes .../2029240f6d1128be89ddc32729463129 | Bin 9 -> 0 bytes .../2f9df8e9888f06aaccaaecb1d59a4fc9 | Bin 90 -> 0 bytes .../6676a3c21dc87cff4d3ba176913692a8 | Bin 710 -> 0 bytes .../7de69e160ce0c51fb5bebb75cab947f7 | Bin 710 -> 0 bytes .../95ccee819a22c21d5f80e3ac3bf8d9f5 | Bin 503 -> 0 bytes .../b05bbcd9f8e51d0ae8a2761b0fd4223b | Bin 129 -> 0 bytes .../c69ed4b3546c78bcaec840d96af53d56 | Bin 628 -> 0 bytes .../c847eff44ad4c3adc9dd06d36b2a59f8 | Bin 720 -> 0 bytes .../cb0ea92ad17f189ecb4adfa207037b12 | Bin 90 -> 0 bytes .../d938c4aed87c8559a4a5a34bf8add27c | Bin 90 -> 0 bytes .../e8bb3a807506a24b00dc81dd75f79cb9 | Bin 90 -> 0 bytes .../e8e8f8ae24bf1bdbc8be2d885732bcee | Bin 129 -> 0 bytes .../2029240f6d1128be89ddc32729463129 | Bin 9 -> 0 bytes .../726fff0f600a0b6e5f8b7fd5fb61a681 | Bin 90 -> 0 bytes readme.md | 4 +- requirements.txt | 4 +- run.py | 8 +- 26 files changed, 731 insertions(+), 356 deletions(-) delete mode 100644 flask_session/15c846f2599d1bfc08cc5cb4a5ed974f delete mode 100644 flask_session/2029240f6d1128be89ddc32729463129 delete mode 100644 flask_session/2f9df8e9888f06aaccaaecb1d59a4fc9 delete mode 100644 flask_session/6676a3c21dc87cff4d3ba176913692a8 delete mode 100644 flask_session/7de69e160ce0c51fb5bebb75cab947f7 delete mode 100644 flask_session/95ccee819a22c21d5f80e3ac3bf8d9f5 delete mode 100644 flask_session/b05bbcd9f8e51d0ae8a2761b0fd4223b delete mode 100644 flask_session/c69ed4b3546c78bcaec840d96af53d56 delete mode 100644 flask_session/c847eff44ad4c3adc9dd06d36b2a59f8 delete mode 100644 flask_session/cb0ea92ad17f189ecb4adfa207037b12 delete mode 100644 flask_session/d938c4aed87c8559a4a5a34bf8add27c delete mode 100644 flask_session/e8bb3a807506a24b00dc81dd75f79cb9 delete mode 100644 flask_session/e8e8f8ae24bf1bdbc8be2d885732bcee delete mode 100644 flask_session_data/2029240f6d1128be89ddc32729463129 delete mode 100644 flask_session_data/726fff0f600a0b6e5f8b7fd5fb61a681 diff --git a/.env.example b/.env.example index 6a1e14c..5057495 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,22 @@ REDIRECT_URI=http://127.0.0.1:5000/callback GENAI_API_KEY=your_gemini_api_key # Flask Configuration +# Required when FLASK_ENV=production; generated per-restart in development FLASK_SECRET_KEY=your_secure_random_key_here FLASK_ENV=development # SERVER_NAME=127.0.0.1:5000 # Optional + +# Dev server (never enable the debugger on a public interface) +FLASK_DEBUG=0 +FLASK_RUN_HOST=127.0.0.1 +FLASK_RUN_PORT=5000 + +# Comma-separated origins allowed to call the API with cookies +# CORS_ORIGINS=http://localhost:8080,http://127.0.0.1:8080 +# Comma-separated origins the OAuth flow may redirect back to (defaults to CORS_ORIGINS) +# ALLOWED_REDIRECT_ORIGINS=http://localhost:8080 +# DEFAULT_FRONTEND_REDIRECT=http://127.0.0.1:8080/preferences + +# Limits +# MAX_PLAYLIST_LENGTH=100 +# MAX_CONTENT_LENGTH=2097152 diff --git a/.gitignore b/.gitignore index da59f65..78a470b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,12 +27,16 @@ ENV/ # Environment Variables .env +.env.* +!.env.example # Flask instance/ .webassets-cache .flask_session/ flask_session/ +flask_session_data/ +*.db # OS .DS_Store diff --git a/UI_Front/package-lock.json b/UI_Front/package-lock.json index dc569a5..1940c36 100644 --- a/UI_Front/package-lock.json +++ b/UI_Front/package-lock.json @@ -912,6 +912,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2312,9 +2329,9 @@ "license": "MIT" }, "node_modules/@remix-run/router": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", - "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "version": "1.23.4", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz", + "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -2328,9 +2345,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", + "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", "cpu": [ "arm" ], @@ -2342,9 +2359,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", + "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", "cpu": [ "arm64" ], @@ -2356,9 +2373,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", + "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", "cpu": [ "arm64" ], @@ -2370,9 +2387,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", + "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", "cpu": [ "x64" ], @@ -2383,10 +2400,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", + "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", + "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", + "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", "cpu": [ "arm" ], @@ -2398,9 +2443,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", + "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", "cpu": [ "arm" ], @@ -2412,9 +2457,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", + "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", "cpu": [ "arm64" ], @@ -2426,9 +2471,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", + "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", "cpu": [ "arm64" ], @@ -2439,10 +2484,52 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", + "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", + "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", + "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", + "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", "cpu": [ "ppc64" ], @@ -2454,9 +2541,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", + "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", + "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", "cpu": [ "riscv64" ], @@ -2468,9 +2569,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", + "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", "cpu": [ "s390x" ], @@ -2482,9 +2583,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", + "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", "cpu": [ "x64" ], @@ -2496,9 +2597,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", + "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", "cpu": [ "x64" ], @@ -2509,10 +2610,38 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", + "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", + "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", + "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", "cpu": [ "arm64" ], @@ -2524,9 +2653,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", + "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", "cpu": [ "ia32" ], @@ -2537,10 +2666,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", + "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", + "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", "cpu": [ "x64" ], @@ -2917,9 +3060,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -3242,9 +3385,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -3252,13 +3395,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3359,10 +3502,22 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3492,14 +3647,15 @@ } }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -3521,29 +3677,46 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -4011,9 +4184,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4202,9 +4375,9 @@ } }, "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==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4694,16 +4867,16 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "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", @@ -4737,16 +4910,16 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "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" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -4875,9 +5048,10 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4907,21 +5081,21 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5000,9 +5174,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5012,40 +5186,52 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">= 0.8" + "node": ">= 6" } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore": { @@ -5228,10 +5414,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5320,9 +5516,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.castarray": { @@ -5904,9 +6100,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5943,9 +6139,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -6183,12 +6379,13 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/picocolors": { @@ -6198,9 +6395,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -6228,9 +6425,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -6247,7 +6444,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6411,10 +6608,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "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/punycode": { "version": "2.3.1", @@ -6427,12 +6627,13 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -6488,18 +6689,18 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/react": { @@ -6621,12 +6822,12 @@ } }, "node_modules/react-router": { - "version": "6.30.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", - "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz", + "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.0" + "@remix-run/router": "1.23.4" }, "engines": { "node": ">=14.0.0" @@ -6636,13 +6837,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", - "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz", + "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.0", - "react-router": "6.30.1" + "@remix-run/router": "1.23.4", + "react-router": "6.30.6" }, "engines": { "node": ">=14.0.0" @@ -6796,13 +6997,13 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.0.tgz", + "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -6812,22 +7013,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.0", + "@rollup/rollup-android-arm64": "4.63.0", + "@rollup/rollup-darwin-arm64": "4.63.0", + "@rollup/rollup-darwin-x64": "4.63.0", + "@rollup/rollup-freebsd-arm64": "4.63.0", + "@rollup/rollup-freebsd-x64": "4.63.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", + "@rollup/rollup-linux-arm-musleabihf": "4.63.0", + "@rollup/rollup-linux-arm64-gnu": "4.63.0", + "@rollup/rollup-linux-arm64-musl": "4.63.0", + "@rollup/rollup-linux-loong64-gnu": "4.63.0", + "@rollup/rollup-linux-loong64-musl": "4.63.0", + "@rollup/rollup-linux-ppc64-gnu": "4.63.0", + "@rollup/rollup-linux-ppc64-musl": "4.63.0", + "@rollup/rollup-linux-riscv64-gnu": "4.63.0", + "@rollup/rollup-linux-riscv64-musl": "4.63.0", + "@rollup/rollup-linux-s390x-gnu": "4.63.0", + "@rollup/rollup-linux-x64-gnu": "4.63.0", + "@rollup/rollup-linux-x64-musl": "4.63.0", + "@rollup/rollup-openbsd-x64": "4.63.0", + "@rollup/rollup-openharmony-arm64": "4.63.0", + "@rollup/rollup-win32-arm64-msvc": "4.63.0", + "@rollup/rollup-win32-ia32-msvc": "4.63.0", + "@rollup/rollup-win32-x64-gnu": "4.63.0", + "@rollup/rollup-win32-x64-msvc": "4.63.0", "fsevents": "~2.3.2" } }, @@ -6983,14 +7194,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -7002,13 +7213,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -7405,17 +7616,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { @@ -7616,9 +7844,9 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", "dependencies": { @@ -7795,15 +8023,18 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", - "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yocto-queue": { diff --git a/backend/app.py b/backend/app.py index e3b7e65..6ca3179 100644 --- a/backend/app.py +++ b/backend/app.py @@ -10,10 +10,9 @@ def create_app(): # Initialize Session Session(app) - # Initialize CORS - # Allowing specific origins as per main2.py but simplified - CORS(app, - resources={r"/*": {"origins": ["http://localhost:8080", "http://127.0.0.1:8080", "http://localhost:8081", "http://127.0.0.1:8081"]}}, + # Initialize CORS - credentialed requests are limited to configured origins + CORS(app, + resources={r"/*": {"origins": Config.CORS_ORIGINS}}, supports_credentials=True) # Register Blueprints diff --git a/backend/config.py b/backend/config.py index f577914..061bcf8 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,31 +1,50 @@ import os import secrets from datetime import timedelta +from urllib.parse import urlparse from dotenv import load_dotenv load_dotenv() + +def _split_env_list(name, default): + raw = os.getenv(name) + if not raw: + return default + return [item.strip() for item in raw.split(',') if item.strip()] + + class Config: # Flask Settings - SECRET_KEY = os.getenv('FLASK_SECRET_KEY') or secrets.token_hex(32) + IS_PRODUCTION = os.getenv('FLASK_ENV') == 'production' + + _secret_key = os.getenv('FLASK_SECRET_KEY') + if not _secret_key: + if IS_PRODUCTION: + raise RuntimeError( + "FLASK_SECRET_KEY must be set when FLASK_ENV=production" + ) + # Development only: ephemeral key, sessions do not survive a restart + _secret_key = secrets.token_hex(32) + SECRET_KEY = _secret_key + # Ensure usage of 127.0.0.1 for localhost consistency SERVER_NAME = os.getenv('SERVER_NAME') # Optional, helpful for url_for - + + # Reject oversized request bodies (playlist cover images are capped at 256KB) + MAX_CONTENT_LENGTH = int(os.getenv('MAX_CONTENT_LENGTH', 2 * 1024 * 1024)) + # Session Settings SESSION_TYPE = 'filesystem' SESSION_FILE_DIR = os.path.join(os.getcwd(), 'flask_session') SESSION_PERMANENT = True PERMANENT_SESSION_LIFETIME = timedelta(hours=1) SESSION_USE_SIGNER = True - + # Cookie Settings - Production vs Dev SESSION_COOKIE_NAME = 'spotify_session' SESSION_COOKIE_HTTPONLY = True - - # In production (non-localhost), these should be strict - # We detect if we are running locally based on DEBUG or explicit env var - IS_PRODUCTION = os.getenv('FLASK_ENV') == 'production' - + if IS_PRODUCTION: SESSION_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = 'None' # Required for cross-site if frontend/backend are separate domains @@ -36,14 +55,32 @@ class Config: SESSION_COOKIE_SAMESITE = 'Lax' # Better for localhost SESSION_COOKIE_DOMAIN = None + # Origins allowed to call the API with credentials + CORS_ORIGINS = _split_env_list('CORS_ORIGINS', [ + "http://localhost:8080", + "http://127.0.0.1:8080", + "http://localhost:8081", + "http://127.0.0.1:8081", + ]) + + # Origins the OAuth flow is allowed to redirect back to + ALLOWED_REDIRECT_ORIGINS = _split_env_list('ALLOWED_REDIRECT_ORIGINS', CORS_ORIGINS) + DEFAULT_FRONTEND_REDIRECT = os.getenv( + 'DEFAULT_FRONTEND_REDIRECT', 'http://127.0.0.1:8080/preferences' + ) + # Spotify OAuth SPOTIFY_CLIENT_ID = os.getenv("CLIENT_ID") SPOTIFY_CLIENT_SECRET = os.getenv("CLIENT_SECRET") SPOTIFY_REDIRECT_URI = os.getenv("REDIRECT_URI") - + # AI / Gemini GENAI_API_KEY = os.getenv("GENAI_API_KEY") + # Playlist limits (bound AI cost and Spotify request volume) + MAX_PLAYLIST_LENGTH = int(os.getenv('MAX_PLAYLIST_LENGTH', 100)) + DEFAULT_PLAYLIST_LENGTH = 20 + @classmethod def validate(cls): """Ensure critical config exists""" @@ -55,3 +92,16 @@ def validate(cls): if missing: raise ValueError(f"Missing required environment variables: {', '.join(missing)}") + + @classmethod + def safe_redirect(cls, url): + """Return url if it points at an allowed origin, else the default redirect.""" + if not url: + return cls.DEFAULT_FRONTEND_REDIRECT + parsed = urlparse(url) + if parsed.scheme not in ('http', 'https') or not parsed.netloc: + return cls.DEFAULT_FRONTEND_REDIRECT + origin = f"{parsed.scheme}://{parsed.netloc}" + if origin in cls.ALLOWED_REDIRECT_ORIGINS: + return url + return cls.DEFAULT_FRONTEND_REDIRECT diff --git a/backend/routes/auth.py b/backend/routes/auth.py index ec507cb..353312c 100644 --- a/backend/routes/auth.py +++ b/backend/routes/auth.py @@ -1,12 +1,47 @@ from flask import Blueprint, request, session, redirect, jsonify, current_app from ..config import Config from ..services.spotify import SpotifyService +import logging import secrets -import time from datetime import datetime +logger = logging.getLogger(__name__) + auth_bp = Blueprint('auth', __name__) + +def get_valid_access_token(): + """Return a non-expired Spotify access token, refreshing it if needed. + + Returns None when the caller is not authenticated; the session is cleared in + that case so stale credentials are not reused. + """ + if 'access_token' not in session: + return None + + if datetime.now().timestamp() <= session.get('expires_at', 0): + return session['access_token'] + + refresh = session.get('refresh_token') + if not refresh: + session.clear() + return None + + try: + spotify = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) + new_tokens = spotify.refresh_token(refresh) + except Exception: + logger.exception("Spotify token refresh failed") + session.clear() + return None + + session['access_token'] = new_tokens.get('access_token') + if 'refresh_token' in new_tokens: + session['refresh_token'] = new_tokens.get('refresh_token') + session['expires_at'] = datetime.now().timestamp() + new_tokens.get('expires_in', 3600) + session.modified = True + return session['access_token'] + @auth_bp.route('/login') def login(): # Verify config @@ -21,12 +56,9 @@ def login(): state = secrets.token_urlsafe(16) session['oauth_state'] = state - # Store where to go after login (defaulting to frontend) - # In main2.py this was hardcoded or parameter driven. - # Adapting to safe defaults. - # If the user is on localhost:8080 (dev), we redirect there. - frontend_url = request.args.get('redirect', 'http://127.0.0.1:8080/preferences') - session['frontend_redirect'] = frontend_url + # Store where to go after login. Only allow-listed origins are accepted so + # the OAuth flow cannot be turned into an open redirect. + session['frontend_redirect'] = Config.safe_redirect(request.args.get('redirect')) # Force session save session.modified = True @@ -56,7 +88,7 @@ def callback(): stored_state = session.get('oauth_state') received_state = request.args.get('state') - if not stored_state or stored_state != received_state: + if not stored_state or not received_state or not secrets.compare_digest(stored_state, received_state): return jsonify({"error": "Invalid state parameter", "details": "Session may have expired or cross-site request detected."}), 400 error = request.args.get('error') @@ -71,8 +103,9 @@ def callback(): spotify = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) try: token_info = spotify.exchange_code_for_token(code, Config.SPOTIFY_REDIRECT_URI) - except Exception as e: - return jsonify({"error": "Token exchange failed", "details": str(e)}), 500 + except Exception: + logger.exception("Spotify token exchange failed") + return jsonify({"error": "Token exchange failed"}), 502 # Store Session session['access_token'] = token_info.get('access_token') @@ -85,44 +118,21 @@ def callback(): profile = spotify.get_user_profile(session['access_token']) session['spotify_user_id'] = profile.get('id') session['spotify_display_name'] = profile.get('display_name') - except Exception as e: - print(f"Warning: Failed to fetch profile on login: {e}") - + except Exception: + logger.exception("Failed to fetch Spotify profile on login") + + # The state is single use + session.pop('oauth_state', None) session.modified = True - - # Redirect back to frontend - redirect_url = session.get('frontend_redirect', 'http://127.0.0.1:8080/') - return redirect(redirect_url) + + # Redirect back to frontend (re-validated in case the stored value is stale) + return redirect(Config.safe_redirect(session.pop('frontend_redirect', None))) @auth_bp.route('/auth/status') def auth_status(): - if 'access_token' not in session: + if not get_valid_access_token(): return jsonify({"authenticated": False}), 401 - - # Check expiry - expires_at = session.get('expires_at', 0) - if datetime.now().timestamp() > expires_at: - # Try refresh - if 'refresh_token' in session: - try: - spotify = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) - new_tokens = spotify.refresh_token(session['refresh_token']) - - session['access_token'] = new_tokens.get('access_token') - # Refresh token might not always be returned in a refresh flow, keep old one if so - if 'refresh_token' in new_tokens: - session['refresh_token'] = new_tokens.get('refresh_token') - - session['expires_at'] = datetime.now().timestamp() + new_tokens.get('expires_in', 3600) - session.modified = True - except Exception as e: - print(f"Refresh failed: {e}") - session.clear() - return jsonify({"authenticated": False}), 401 - else: - session.clear() - return jsonify({"authenticated": False}), 401 - + return jsonify({ "authenticated": True, "user": session.get('spotify_display_name'), diff --git a/backend/routes/playlist.py b/backend/routes/playlist.py index 778bf3b..7d64142 100644 --- a/backend/routes/playlist.py +++ b/backend/routes/playlist.py @@ -1,38 +1,94 @@ from flask import Blueprint, request, session, jsonify +import base64 +import binascii import concurrent.futures +import logging +import re +import requests from ..config import Config from ..services.spotify import SpotifyService from ..services.ai import AIService +from .auth import get_valid_access_token + +logger = logging.getLogger(__name__) playlist_bp = Blueprint('playlist', __name__) +# Spotify accepts at most 100 track URIs per request +MAX_TRACK_URIS = 100 +MAX_NAME_LENGTH = 100 +MAX_DESCRIPTION_LENGTH = 300 +MAX_QUERY_LENGTH = 200 +# Spotify rejects cover images larger than 256KB of base64 payload +MAX_COVER_IMAGE_BYTES = 256 * 1024 + +TRACK_URI_RE = re.compile(r'^spotify:track:[A-Za-z0-9]{22}$') + + +def _clamp_playlist_length(value): + if isinstance(value, list): + value = value[0] if value else None + try: + length = int(value) + except (TypeError, ValueError): + return Config.DEFAULT_PLAYLIST_LENGTH + return max(1, min(length, Config.MAX_PLAYLIST_LENGTH)) + + +def _validate_track_uris(uris): + """Return (uris, error). Only well-formed Spotify track URIs are accepted.""" + if not isinstance(uris, list) or not uris: + return None, "No tracks provided" + if len(uris) > MAX_TRACK_URIS: + return None, f"At most {MAX_TRACK_URIS} tracks can be added at once" + for uri in uris: + if not isinstance(uri, str) or not TRACK_URI_RE.match(uri): + return None, "Invalid track URI" + return uris, None + + +def _validate_cover_image(image): + """Return (image, error) for an optional base64 (or data URL) cover image.""" + if image is None: + return None, None + if not isinstance(image, str): + return None, "Invalid image" + payload = image.split(",", 1)[1] if "," in image else image + payload = payload.strip() + try: + decoded = base64.b64decode(payload, validate=True) + except (binascii.Error, ValueError): + return None, "Image must be base64 encoded JPEG data" + if len(payload) > MAX_COVER_IMAGE_BYTES or not decoded: + return None, "Image exceeds the 256KB limit" + return payload, None + + +def _clean_text(value, default, max_length): + if not isinstance(value, str) or not value.strip(): + return default + return value.strip()[:max_length] + + @playlist_bp.route('/Playlist_Generator', methods=['POST']) @playlist_bp.route('/Generate_Preview', methods=['POST']) def generate_preview(): # 1. Auth Check - if 'access_token' not in session: + access_token = get_valid_access_token() + if not access_token: return jsonify({"error": "Not authenticated", "redirect": "/login"}), 401 - + # 2. Get Params - data = request.get_json() or {} + data = request.get_json(silent=True) or {} preferences = data.get('preferences') - if not preferences: + if not isinstance(preferences, dict) or not preferences: return jsonify({"error": "No preferences provided"}), 400 - - playlist_length = preferences.get("playlistLength", 20) - # Handle if frontend sends a list (legacy support) - if isinstance(playlist_length, list): - playlist_length = playlist_length[0] - - try: - playlist_length = int(playlist_length) - except: - playlist_length = 20 + + playlist_length = _clamp_playlist_length(preferences.get("playlistLength")) # 3. AI Generation # Request extra songs to buffer against those not found on Spotify - # Increase buffer to 100% of requested length or at least 10 songs to be safe - buffer_count = max(10, int(playlist_length * 1.0)) + buffer_count = max(10, playlist_length) target_ai_count = playlist_length + buffer_count ai_service = AIService() @@ -41,16 +97,16 @@ def generate_preview(): try: # Generate raw song list with buffer ai_songs = ai_service.generate_playlist_params(preferences, count=target_ai_count) - except Exception as e: - return jsonify({"error": "AI Generation failed", "details": str(e)}), 500 + except Exception: + logger.exception("AI playlist generation failed") + return jsonify({"error": "AI Generation failed"}), 502 # 4. Spotify Search (Parallelized) found_tracks = [] - - # Extract token from session in the main thread - access_token = session['access_token'] - + def search_worker(token, song_info): + if not isinstance(song_info, dict): + return None return spotify_service.search_track( token, song_info.get('name'), @@ -67,8 +123,8 @@ def search_worker(token, song_info): track = future.result() if track: found_tracks.append(track) - except Exception as e: - print(f"Search error: {e}") + except Exception: + logger.exception("Spotify track search failed") if not found_tracks: return jsonify({"error": "No songs found on Spotify matching the criteria"}), 404 @@ -103,17 +159,21 @@ def search_worker(token, song_info): @playlist_bp.route('/Create_Playlist', methods=['POST']) def create_playlist(): - if 'access_token' not in session: + access_token = get_valid_access_token() + if not access_token: return jsonify({"error": "Not authenticated", "redirect": "/login"}), 401 - data = request.get_json() or {} - name = data.get('name', 'AI Generated Bundle') - description = data.get('description', 'Generated by PlaylistAI') - uris = data.get('uris', []) - image = data.get('image') # Base64 string + data = request.get_json(silent=True) or {} + name = _clean_text(data.get('name'), 'AI Generated Bundle', MAX_NAME_LENGTH) + description = _clean_text(data.get('description'), 'Generated by PlaylistAI', MAX_DESCRIPTION_LENGTH) + + uris, error = _validate_track_uris(data.get('uris')) + if error: + return jsonify({"error": error}), 400 - if not uris: - return jsonify({"error": "No tracks provided"}), 400 + image, error = _validate_cover_image(data.get('image')) + if error: + return jsonify({"error": error}), 400 spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) @@ -121,12 +181,12 @@ def create_playlist(): user_id = session.get('spotify_user_id') if not user_id: # Try to fetch if missing - profile = spotify_service.get_user_profile(session['access_token']) + profile = spotify_service.get_user_profile(access_token) user_id = profile['id'] session['spotify_user_id'] = user_id playlist = spotify_service.create_playlist( - session['access_token'], + access_token, user_id, name=name, description=description, @@ -135,7 +195,7 @@ def create_playlist(): # Add Tracks spotify_service.add_tracks_to_playlist( - session['access_token'], + access_token, playlist['id'], uris ) @@ -143,46 +203,38 @@ def create_playlist(): # Upload Image if provided if image: try: - # Add slight delay to ensure playlist is ready? - # Spotify API usually handles it fine, but sometimes it takes a moment. spotify_service.upload_playlist_cover( - session['access_token'], + access_token, playlist['id'], image ) - except Exception as img_err: - print(f"Failed to upload image: {img_err}") + except Exception: # Don't fail the whole request, just log it + logger.exception("Failed to upload playlist cover image") return jsonify({ "playlist_id": playlist['id'], "message": "Playlist created successfully" }) - except Exception as e: - print(f"Playlist creation failed: {e}") - return jsonify({"error": "Failed to create playlist on Spotify", "details": str(e)}), 500 + except Exception: + logger.exception("Playlist creation failed") + return jsonify({"error": "Failed to create playlist on Spotify"}), 502 @playlist_bp.route('/Search_Track', methods=['GET']) def search_spotify_track(): - if 'access_token' not in session: + access_token = get_valid_access_token() + if not access_token: return jsonify({"error": "Not authenticated"}), 401 - query = request.args.get('q') + query = request.args.get('q', '').strip() if not query: return jsonify({"error": "Missing query"}), 400 + query = query[:MAX_QUERY_LENGTH] spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) - - # We'll use a direct search here, reusing search_track logic or calling raw search - # Since search_track in service is specific to track/artist, let's just do a raw search here or add a general search to service. - # For now, let's use the service's existing base URL but we need a general search. - # Actually, let's just use requests here for simplicity or extend service. - # Extending service is cleaner. - + try: - # Reuse search_track logic but treat query as song name and ignore artist if not provided? - # Or better, just call Spotify Search API directly here for flexible search params = { "q": query, "type": "track", @@ -192,12 +244,13 @@ def search_spotify_track(): response = requests.get( f"{SpotifyService.BASE_URL}/search", - headers=spotify_service.get_auth_headers(session['access_token']), - params=params + headers=spotify_service.get_auth_headers(access_token), + params=params, + timeout=10 ) if response.status_code != 200: - return jsonify({"error": "Spotify search failed"}), response.status_code + return jsonify({"error": "Spotify search failed"}), 502 data = response.json() tracks = data.get('tracks', {}).get('items', []) @@ -221,26 +274,28 @@ def search_spotify_track(): return jsonify(results) - except Exception as e: - return jsonify({"error": str(e)}), 500 + except Exception: + logger.exception("Spotify track search failed") + return jsonify({"error": "Spotify search failed"}), 502 @playlist_bp.route('/Get_Playlists', methods=['GET']) def get_playlists(): - if 'access_token' not in session: + access_token = get_valid_access_token() + if not access_token: return jsonify({"error": "Not authenticated"}), 401 - + spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET) try: - # Simplified for now, just getting user's playlists - # Logic from main2.py could be adapted if specific playlist fetching is needed - # But this route seemed generic in main2.py - params = {"limit": 50} response = requests.get( f"{SpotifyService.BASE_URL}/me/playlists", - headers=spotify_service.get_auth_headers(session['access_token']), - params=params + headers=spotify_service.get_auth_headers(access_token), + params=params, + timeout=10 ) + if response.status_code != 200: + return jsonify({"error": "Failed to fetch playlists"}), 502 return jsonify(response.json()) - except Exception as e: - return jsonify({"error": str(e)}), 500 + except Exception: + logger.exception("Failed to fetch user playlists") + return jsonify({"error": "Failed to fetch playlists"}), 502 diff --git a/backend/services/spotify.py b/backend/services/spotify.py index 5c6f496..8ca11eb 100644 --- a/backend/services/spotify.py +++ b/backend/services/spotify.py @@ -29,7 +29,7 @@ def exchange_code_for_token(self, code, redirect_uri): 'grant_type': "authorization_code" } - response = requests.post(self.AUTH_URL, data=data, headers=headers) + response = requests.post(self.AUTH_URL, data=data, headers=headers, timeout=10) if response.status_code != 200: raise Exception(f"Token exchange failed: {response.text}") @@ -46,7 +46,7 @@ def refresh_token(self, refresh_token): 'refresh_token': refresh_token } - response = requests.post(self.AUTH_URL, data=data, headers=headers) + response = requests.post(self.AUTH_URL, data=data, headers=headers, timeout=10) if response.status_code != 200: raise Exception(f"Token refresh failed: {response.text}") @@ -122,7 +122,8 @@ def create_playlist(self, access_token, user_id, name, description="Generated by response = requests.post( url, headers=self.get_auth_headers(access_token), - json=data + json=data, + timeout=10 ) response.raise_for_status() return response.json() @@ -139,7 +140,8 @@ def add_tracks_to_playlist(self, access_token, playlist_id, uris): response = requests.post( url, headers=self.get_auth_headers(access_token), - json=data + json=data, + timeout=10 ) response.raise_for_status() return response.json() @@ -147,7 +149,8 @@ def add_tracks_to_playlist(self, access_token, playlist_id, uris): def get_user_profile(self, access_token): response = requests.get( f"{self.BASE_URL}/me", - headers=self.get_auth_headers(access_token) + headers=self.get_auth_headers(access_token), + timeout=10 ) if response.status_code != 200: raise Exception(f"Failed to fetch profile: {response.text}") @@ -175,7 +178,8 @@ def upload_playlist_cover(self, access_token, playlist_id, image_b64): response = requests.put( url, headers=headers, - data=image_b64 + data=image_b64, + timeout=15 ) if response.status_code == 202: diff --git a/flask_session/15c846f2599d1bfc08cc5cb4a5ed974f b/flask_session/15c846f2599d1bfc08cc5cb4a5ed974f deleted file mode 100644 index d4ef5ebc1a906024a23084e5acebd8f3725cf413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 zcmaJkNYi~^3Mrmqfv5%r||Nhz46}_g#UZXD94Q7$f z$i6mkswGYxSCw(FO!82ZrQEQ3Sn#Ql1RQ3(I?iBH)0;J_rJ`6XR4%I%eyaeqR8?xq z^fjL_XN#5UQJ4!FI)uLIO7y-Wfd}g&97xU#Pgf12PK;cc&m+Qx=h&lIhNEX9+0T%p zFqJJ1$!KYEo51D`sib_J=W@u&@_fzic*TR+nf6QZD9+^4lZ!lyk-|rPjuB`|?@Qb! z+3wXXwoV%WR9F?pN@1rtJ2I%g68G~RunDu!dJaCVcBHBmY^du7dsLdhH0Zs2ILPHN z(jBOQQrJeE#0?i(EP}|MvZ8O&KqxLt-(qyYC_VjKwEZ+$Pa@maPes1k2!-NddN76- zx$g0n+l<+bSI)<*00cGxL;wH) diff --git a/flask_session/2f9df8e9888f06aaccaaecb1d59a4fc9 b/flask_session/2f9df8e9888f06aaccaaecb1d59a4fc9 deleted file mode 100644 index 4ec32e6b5bd2277db4c958f01ccbc79b300dfa55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmX>;StYZ9b*eW51k_H^=;4YlNG-}u%uCHHnbOfCm{ydZSCX2S5?_>>l3A3RTr#Cc pF{7lUz)D{~CqFqcCnLYO#LB|J!a%>EC^ap$C^auRwRlRY9srD|Aaei! diff --git a/flask_session/6676a3c21dc87cff4d3ba176913692a8 b/flask_session/6676a3c21dc87cff4d3ba176913692a8 deleted file mode 100644 index ef71f9760df88418580eaafc3e400963f4ce6ca9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 710 zcmaKqJCBr50EJf*o7IgmF(&pFHnU-1nPEDa7cehgvkOCWbGf`91Ki8NKw`3qjRhtC z7F&OZl|RMNxUHu=IVU+e`5u3K2p?a3`~LjuuRix5yK9iUIE6>|SoUALml-^j0U#wT z-M)KQEDNU*w76`{*{Vtg4|mo{=KJmyM93uslv(T^dy{^4bv0Gz_v}4DEFGt_&5*Ez zcvdqEmL*R|Y4Ff08>iZi>&g$7^OdL*6>LX(MN^hFTzfXv6s+SndC5r(%?jbFU7F2e zY9ET~ZUL7hVpPT#gV-42Br9HPL>h<{-cvSO!q70pN!&O> zNJ*x9SnuS6U7Rjx!ZABPp=KtZG78FEE!L^-K*c6{TUul=prDf^#pdC_@ARQU z@Mq-Z6{gwoXm8|#8*i0OoL6!&Jk--R7$^}3j=r|gDPtK; zx708~%-+!GIJ52X*jD@Q2D=`1N$~H-?hCK-wLY1|^-9x0tj#>1!?IsYg)nk80u()J zhfDEXWl%INGiZ0aawO$R%^g~Ljw3@&k+PQP-5G5wZvzcA(riZBBfK`-F;|kkM39VH zuQo+xF7LCc6_}i5G9HyHVlm}(HtxG?_ne29Spb&3!2gtP3-;HW?lsA?GV~g7A}$7@ z(|7M#hDMXfzH6MIv>jXxvy`qfT(Euj_9^XzBu`)ijxfCd^}ou^Q^ieTm|Wn?@r6n^ SAv8s?pZq`OEy~jVbo(1kLHkJn diff --git a/flask_session/7de69e160ce0c51fb5bebb75cab947f7 b/flask_session/7de69e160ce0c51fb5bebb75cab947f7 deleted file mode 100644 index cff64d3db2f2f3aaf49ec05f0be55f24e1447cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 710 zcmaKqxsKCd0E8DLnq5{j2#KD8CM(+;C$^x#XB<0;W3QdVjpgHy@B9CqI1&;P4TVed zBow>_55Ox>@CpP3EmMwWq|xYm`}0-w;mPN(kFWmd{qVZKwo6cFP7ZQ7eClrs2f@&8 zpaa3szrc68&0yRl)z2w6o$(|`FN0v{pE{lgn#P7j0`g%;Ji5AKHI`R*^T~on0&HRV z;7FrLiz-pv`HJf#)p%K0A-0)ex>2?V%1YNz%M}lCuZo?-9E&NkZn!MK)d0 zvU7ZJ^zb2Q%fJ#ELRY3%La;0q>w$1$RP~H$EbgVOj&_lpCuN~1Vvz`$0}>pO_H;$g zI5Rtlx<^*9LJ4m;TUC{cJs!JdZJ32@ZBmK_XR=#uwwknCdhE>QsgYltC$bbv?omM; zC&cGYWLTF9AsCC)y(yoUq`soI+{kV5ToBjuw(KU`xYoOr24+QB6OEZ#IgHrGBYo3U zvHc=CD0_efw(Bcg%n9@3(BIbJP=hA?`(yu+(ReJ7j`y{!;>h(3+br$N1qw4Zjr(-M zMA=9^E8%v~O8X)_C`-Aco?p}z*FGP=yN9XQOG@AEg`e%yTNpEYFxqeEw- z2Gn-s5B*E>p77|{9d~{RUB7PeA|u+OuE?Q({x9uEO_@5KojchDsQ*=N|5ZTdMCk>- Xvc6CW(uTk=@?G%Tyz`1|KzF|Y{Au~* diff --git a/flask_session/95ccee819a22c21d5f80e3ac3bf8d9f5 b/flask_session/95ccee819a22c21d5f80e3ac3bf8d9f5 deleted file mode 100644 index 20f37c08fed14917474af191791bf98a275cf62c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 503 zcmXYt%dV3^06=x&=0ut}HWeVCo`;te?(O22?dZ$R;xYg^LZ7ddsDFILx+Qd^++0Z2~RC3n$lvaV6EK zc5oVNZ~5eG?UPPM#9gw!Rf=lsY5lHV5BcU%o-2}6sqlD-SZTDe?3kB;$CZIDBv4I8 zG)=+R6;E69$UmWEEhZ$r^}sKOB=txsNx~HZb>+--qLbYCgoQ$M8jYl;XwY-q0g*l~ z<<%0#g=>+6 z>Hdb2gpw}%f9n2q?rOJ?^EnNDI=IrF?KBY%6ttdaQBsDK3z9=;%vnVyWWJ-@>MoCG5mA-yXesW?? cMt*ULm4$(Yfqp?zYFcVhYF=_`@sv_M0FmS`5C8xG diff --git a/flask_session/c69ed4b3546c78bcaec840d96af53d56 b/flask_session/c69ed4b3546c78bcaec840d96af53d56 deleted file mode 100644 index 9255e6fa06b692b3b5b21389d4d5e02146473e02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcmXYvJ+G5c0EVw7E>|5~+)Xrbxn4>srFWy{qZBBgrR6TL+}8_itav&%?b}z$yh9%+UB_c%6d=MP7{ng5&Vs)jOdT zWK%!tvgDbIbCotMI}Y!{D$fwi0CB|m~ol-KBqAsFJZs9*>y zm>pp~4sXEm#@a)90<-ZzzPP(v8mpuT)#N}O)F#CZkn#F#Xj)*LmKe8Q8pFkYe|G&N z#aj}pWXjs6lsmEMpxX)#ZEaO(stxJ!gg=*%9L!lzfVew#cQ|iS5(cbKt5VW0=6J_xJn~G2HzJ!Zb#ZfHrs7JyiX;y=yn=f-{}hMucY$!r!YwB{}!lvu8>>kR?zpt*!ZYWUdG bWanme4{>q(LhS)GitHchQ}Zjgn=gL>ZfD!r diff --git a/flask_session/c847eff44ad4c3adc9dd06d36b2a59f8 b/flask_session/c847eff44ad4c3adc9dd06d36b2a59f8 deleted file mode 100644 index 36f5c7cb24e5ac21620647dafac360e2c94b1abc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 zcmaKqy^hmB0E7=Dnu8M!I*Epgg2t!Bf5eF596O2aICg%V#21RyI$p2;)^@zkC60uI zL_^`yybrtvuRzHQ5D?cgG@4&OUq_#weEt6T;;%loZ<Ba_xaiON)4Ri}z=5GH zc01k}I%>6iOVFLS37ORP5?RnQ)sMydd<{Ffrd6vr7@%6REXpS4(d^N@Vb4 z@-h|;XKM>|GY1D}!N`40Q8pUwW{Rm(wL_{B0w2=}C6i<>#+Fjn0Vf{~JxpCFjV8O4 zk@XCZ<6fT8nh7-sc9C*${4ih_wkSF)AX9d;IY!1dii2ryJXlC^Mq_cn1|TnRIb~TK z5a@P_9d>~4!s#~l#%c$LjwA|Qtcd}eB&QRZ2WW*X_vi?8Hym{>!sV#%TBXG&iaO@A z`C@!b(#aj;>ssY;_LRxhrQz3i9Aip;+BR1u3Q9!7e~UJs<(Uc&Gj%va;ZZ$-fC=0T ziM@m1B~dRyo=a={0oUr$Kxf9yv#Y5VUXpzDiWRRS%^*`k5 e0YL;t=W(u!^88PfQnu3(Zt)-UW}B0CfAbrD>;Cls diff --git a/flask_session/cb0ea92ad17f189ecb4adfa207037b12 b/flask_session/cb0ea92ad17f189ecb4adfa207037b12 deleted file mode 100644 index 2e0b6c27e4f47cb85b7d7bbac26d20fa938aebc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmaDqStYZ9b*eW51k_H^=;4YlNG-}u%uCHHnbOfCm{ydZSCX2S5?_>>l3A3RTr#Cc pF{7lUz)D{~CqFqcCnLYO#LB|J!a%>EC^ap$C^auRwRlRY9sruRAe#UH diff --git a/flask_session/d938c4aed87c8559a4a5a34bf8add27c b/flask_session/d938c4aed87c8559a4a5a34bf8add27c deleted file mode 100644 index 13637c479fa481ab5d1272c541037a4b5162e798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmWHXtdiNlI@Oy20&1sd^l-%&q!#5S=B4J9OzG$mOe@OID@n~ui7!e`$t+4uE}7D! pm{C$vV5P60lb@WJlaXIsVr5}qVW3}7l$w@Wl$w{ET0Esx4*+8;AN>FT diff --git a/flask_session/e8bb3a807506a24b00dc81dd75f79cb9 b/flask_session/e8bb3a807506a24b00dc81dd75f79cb9 deleted file mode 100644 index 50db2b5a451f60a6d20917808d9beef3bce9758e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmdm1s+`%tI@Oy20&1sd^l-%&q!#5S=B4J9OzG$mOe@OID@n~ui7!e`$t+4uE}7D! pm{C$vV5P60lb@WJlaXIsVr5}qVW3}7l$w@Wl$w{ET0Esx4*+}TAVUBE diff --git a/flask_session/e8e8f8ae24bf1bdbc8be2d885732bcee b/flask_session/e8e8f8ae24bf1bdbc8be2d885732bcee deleted file mode 100644 index 22155a2181180a60bfe585623ed8f074a31edf4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmXT^sFK;hI<<%a0&1sd^l-%&q!#5S=B4J9OzG(1&QB~Y$%rp5Ni0d7(j#VRX_n<( zmTqF`7wYL&MoCG5mA-yXesW?? cMt*ULm4$(Yfqp?zYFcVhYF=_`@sv_M0C8b1SpWb4 diff --git a/flask_session_data/2029240f6d1128be89ddc32729463129 b/flask_session_data/2029240f6d1128be89ddc32729463129 deleted file mode 100644 index 8b04914a5e6ad4858df0019a6abe09326d3863de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9 QcmZQzU|?uq^=8xq00XW800000 diff --git a/flask_session_data/726fff0f600a0b6e5f8b7fd5fb61a681 b/flask_session_data/726fff0f600a0b6e5f8b7fd5fb61a681 deleted file mode 100644 index 251018a22b30ae359ba3e0c3ef064d8b0c368627..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmdN|sFK;hI@Oy20&1sd^l-%&q!#5S=B4J9OzG$mOe@OID@n~ui7!e`$t+4uE}7D! pm{C$vV5P60lb@WJlaXIsVr5}qVW3}7l$w@Wl$w{ET0Esx4**(IALIZ4 diff --git a/readme.md b/readme.md index 66c91f8..0035849 100644 --- a/readme.md +++ b/readme.md @@ -56,7 +56,9 @@ Spotify_Project/ ```bash python run.py ``` - The server listens on `http://0.0.0.0:5000/` and stores sessions under `flask_session/` by default. + The server listens on `http://127.0.0.1:5000/` and stores sessions under `flask_session/` by default. + Host, port, and the Werkzeug debugger are controlled by `FLASK_RUN_HOST`, `FLASK_RUN_PORT`, and + `FLASK_DEBUG` (the debugger allows remote code execution, so it is off by default). ## Frontend Setup 1. **Install dependencies** diff --git a/requirements.txt b/requirements.txt index 9e69386..25b9e9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ pydantic_core==2.27.2 pyparsing==3.2.1 python-dotenv==1.0.1 redis==5.2.1 -requests==2.32.3 +requests==2.32.5 rsa==4.9 sniffio==1.3.1 spotipy==2.24.0 @@ -48,6 +48,6 @@ tenacity==9.1.2 tqdm==4.67.1 typing_extensions==4.12.2 uritemplate==4.1.1 -urllib3==2.3.0 +urllib3==2.5.0 websockets==15.0.1 Werkzeug==3.1.3 diff --git a/run.py b/run.py index ec0455c..148c027 100644 --- a/run.py +++ b/run.py @@ -6,5 +6,9 @@ if __name__ == "__main__": # Ensure session dir exists os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True) - # Run - app.run(host='0.0.0.0', port=5000, debug=True) + # The Werkzeug debugger allows arbitrary code execution, so it stays off + # unless explicitly enabled for local development. + debug = os.getenv('FLASK_DEBUG', '0').lower() in ('1', 'true', 'yes') + host = os.getenv('FLASK_RUN_HOST', '127.0.0.1') + port = int(os.getenv('FLASK_RUN_PORT', 5000)) + app.run(host=host, port=port, debug=debug)