diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0348923b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" # Replace with your ecosystem (e.g., pip, docker, bundler) + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + target-branch: "security-fix" # Dependabot will create PRs against this branch diff --git "a/Fas 1 \342\200\223 Hotmodellering & Kravspecifikation (STRIDE 66915aa349c8420da7f169a5ab8dd5c9.md" "b/Fas 1 \342\200\223 Hotmodellering & Kravspecifikation (STRIDE 66915aa349c8420da7f169a5ab8dd5c9.md" new file mode 100644 index 00000000..237b771a --- /dev/null +++ "b/Fas 1 \342\200\223 Hotmodellering & Kravspecifikation (STRIDE 66915aa349c8420da7f169a5ab8dd5c9.md" @@ -0,0 +1,54 @@ +# Fas 1 – Hotmodellering & Kravspecifikation (STRIDE-E + OWASP 2025) + + + +## Översikt + +Dokumentet utgör **Fas 1** av grupparbetet och är underlaget för kravspecifikationen som implementeras i kommande faser. Varje rad i tabellen nedan består av fyra delar: + +1. **STRIDE-E-kategori** – vilken typ av hot vi adresserar. +2. **OWASP Top 10 (2025)** – mappning till en etablerad branschstandard. +3. **Säkerhetskrav** – det konkreta, verifierbara kravet i vår kravspecifikation. +4. **Motivering** – argumentet vi använder i presentationen för *varför* kravet finns. + +--- + +## Hotmodelleringstabell + +| **STRIDE-E** | **OWASP Top 10 (2025)** | **Säkerhetskrav** | **Motivering** | +| --- | --- | --- | --- | +| **S — Spoofing** *(Identitetsförfalskning)* | **A07:** Authentication Failures | **Lösenordslös inloggning:** Systemet ska tillåta autentisering via **Passkeys (WebAuthn)** utan traditionella lösenord. | Genom att fasa ut lösenord eliminerar vi själva grundorsaken till läckta konton. Passkeys är kryptografiskt låsta till vår domän, vilket gör plattformen immun mot nätfiske (phishing) och brute-force. | +| **T — Tampering** *(Manipulering)* | **A05:** Injection | **Strikt indatavalidering:** Systemet ska i backend validera att meddelanden är rensade (saniterade) och mellan **3–140 tecken**. | Genom att aldrig lita på data från frontend och tvinga fram validering i vår Express-API skyddar vi databasen från skadlig kod (SQL/NoSQL Injection) och XSS-attacker. | +| **R — Repudiation** *(Förnekande)* | **A09:** Security Logging & Monitoring Failures | **Granskningslogg:** Systemet ska logga **tidsstämpel, användar-ID och händelsetyp** för alla raderade eller ändrade inlägg. | Om en säkerhetsincident inträffar (t.ex. radering av viktig data) måste vi ha spårbarhet. Utan loggning är vi blinda och användare kan förneka sina handlingar i systemet. | +| **I — Information Disclosure** *(Informationsläckage)* | **A02:** Security Misconfiguration | **Skyddade hemligheter:** Inga känsliga API-nycklar eller lösenord får checkas in i Git, utan ska hanteras via dolda **`.env`-filer**. | Att råka publicera produktionsnycklar på GitHub är en av de vanligaste (och farligaste) felkonfigureringarna. Denna rutin minimerar vår attackyta och skyddar konfidentiell data. | +| **D — Denial of Service** *(Överbelastning)* | **A05:** Insecure Design | **Hastighetsbegränsning (Rate Limiting):** API:et ska ha en gräns på antalet tillåtna anrop **per minut och IP-adress**. | För att säkerställa applikationens tillgänglighet måste vi införa resursbegränsningar. Rate limiting skyddar vår backend från att krascha om den utsätts för automatiserad spam eller en överbelastningsattack. | +| **E — Elevation of Privilege** *(Behörighetsökning)* | **A01:** Broken Access Control | **Auktoriseringskontroll:** Backend måste alltid validera att meddelandets **`author_id`** matchar sessionens inloggade användare vid radering/redigering. | Broken Access Control är webbens största sårbarhet. Vi förhindrar behörighetsökning genom att säkerställa att en vanlig användare aldrig kan manipulera någon annans inlägg, oavsett vad frontenden skickar för anrop. | + +--- + +## Sammanfattning av kraven + +- **K1 – Autentisering:** Passkeys/WebAuthn istället för lösenord. +- **K2 – Indatavalidering:** Sanitering + längdkontroll (3–140 tecken) i backend. +- **K3 – Loggning:** Granskningslogg med tidsstämpel, användar-ID och händelsetyp för mutationer. +- **K4 – Hemligheter:** Inga secrets i Git; allt via `.env` + `.gitignore`. +- **K5 – Tillgänglighet:** Rate limiting per IP och minut på API. +- **K6 – Auktorisering:** `author_id`-kontroll mot session vid alla mutationer. + +--- + +## Referenser + +- [STRIDE threat model (Microsoft)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats) +- [OWASP Top 10 (2025)](https://owasp.org/Top10/) +- [WebAuthn / Passkeys (W3C)](https://www.w3.org/TR/webauthn-3/) + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..034e8480 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/backend/config/db.js b/backend/config/db.js index a4257e74..6f2720ac 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,13 +1,27 @@ import mongoose from "mongoose" + +// [KRAV K4] Skyddade hemligheter (STRIDE: Information Disclosure) +// OWASP A02: Security Misconfiguration -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/messages" +// FÖRE: const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/messages" +// - Om MONGO_URL saknades i .env anslöt servern tyst till en lokal databas utan autentisering: +// farligt om det sker i produktion av misstag. -mongoose.connect(mongoUrl) +// EFTER: Servern kraschar omedelbart om MONGO_URL saknas. +// - MONGO_URL innehåller ofta användarnamn och lösenord för databasen +// - Den ska ALDRIG hårdkodas i koden utan hanteras via .env +// - .env ligger i .gitignore och checkas aldrig in i Git +if (!process.env.MONGO_URL) throw new Error("MONGO_URL is not set in .env") +const mongoUrl = process.env.MONGO_URL + +mongoose.connect(mongoUrl) + mongoose.connection.once("open", () => { console.log("Connected to MongoDB") }) - + mongoose.connection.on("error", err => { console.error("connection error:", err) }) + \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index e3bdf47c..11ac60b6 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -1,12 +1,21 @@ import jwt from "jsonwebtoken" import { User } from "../models/User.js" +// [KRAV K6] Autentiseringsmiddleware (STRIDE: Elevation of Privilege) +// OWASP A01: Broken Access Control + +// Denna middleware verifierar JWT-token och sätter req.user. +// KRITISKT: Den MÅSTE användas på alla endpoints som ändrar data (POST, PATCH, DELETE). +// BUGGEN som hittades i Fas 1: DELETE /messages/:id saknade denna middleware helt, +// vilket innebar att vem som helst kunde radera vilkens meddelande som helst. + export const authenticateUser = async (req, res, next) => { const token = req.headers.authorization?.replace("Bearer ", "") if (!token) { return res.status(401).json({ success: false, message: "No token provided" }) } try { + // [KRAV K4] JWT_SECRET hämtas från .env aldrig hårdkodat const decoded = jwt.verify(token, process.env.JWT_SECRET) const user = await User.findById(decoded.userId) if (!user) { diff --git a/backend/models/Message.js b/backend/models/Message.js index dbca0001..ee88c900 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -1,18 +1,34 @@ import mongoose from "mongoose" const messageSchema = new mongoose.Schema({ + + // [KRAV K2] Strikt indatavalidering (STRIDE: Tampering / OWASP A05: Injection) + + // FÖRE: { type: String, required: true } + // - Ingen min- eller maxlängd: tomma strängar och enorma meddelanden godkändes + // - Ingen trim: whitespace-meddelanden (" ") passerade required-kontrollen + + // EFTER: minlength: 3, maxlength: 140, trim: true + // - trim tar bort mellanslag i början och slutet + // - minlength 3 förhindrar meningslösa meddelanden + // - maxlength 140 skyddar mot databasöverbelastning + // - Valideringen sker i Mongoose (backend), vi litar ALDRIG på frontend + message: { type: String, - required: true + required: [true, "Message is required"], + minlength: [3, "Message must be at least 3 characters"], + maxlength: [140, "Message cannot exceed 140 characters"], + trim: true, }, user: { type: mongoose.Schema.Types.ObjectId, ref: "User", }, -createdAt: { + createdAt: { type: Date, default: Date.now, }, }) -export const Message = mongoose.model("Message", messageSchema) +export const Message = mongoose.model("Message", messageSchema) \ No newline at end of file diff --git a/backend/models/User.js b/backend/models/User.js index 266a60b4..18e800e8 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -14,10 +14,21 @@ const userSchema = new mongoose.Schema({ unique: true, lowercase: true, }, + + // [KRAV K1] Lösenordslös inloggning — framtida ändring + + // NULÄGE: password-fältet lagrar en bcrypt-hash av användarens lösenord. + // bcrypt med salt rounds 10 är en bra implementation, men lösenord i sig är sårbara för phishing och brute force. + + // FRAMTIDA IMPLEMENTATION (Passkeys/WebAuthn): + // - Lägg till fält: passkeyCredentialId (String) och passkeyPublicKey (Buffer) + // - Gör password-fältet valfritt (required: false) för Passkey-användare + // - Passkeys binder autentisering kryptografiskt till domänen + password: { type: String, required: true, }, }) -export const User = mongoose.model("User", userSchema) +export const User = mongoose.model("User", userSchema) \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index d8c4b3cd..b1e0a0fb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,8 +13,9 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-list-endpoints": "^6.0.0", + "express-rate-limit": "^8.5.2", "helmet": "^7.1.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "9.0.0", "mongoose": "^8.4.0", "nodemon": "^3.1.3" } @@ -39,18 +40,6 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz", @@ -619,6 +608,24 @@ "node": ">=10" } }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.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", @@ -990,6 +997,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "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", @@ -1051,25 +1067,19 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "license": "MIT", "dependencies": { "jws": "^3.2.2", - "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", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/ms": { @@ -1108,46 +1118,10 @@ "node": ">=12.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==", + "node_modules/lodash": { + "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/make-dir": { @@ -1552,18 +1526,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nodemon/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -1708,9 +1670,9 @@ } }, "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -1815,12 +1777,15 @@ "license": "MIT" }, "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -1976,18 +1941,6 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -2054,7 +2007,7 @@ } }, "node_modules/tar": { - "version": "6.2.1", + "version": "> 7.5.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "deprecated": "Old versions of tar 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", diff --git a/backend/package.json b/backend/package.json index 1f47ffb6..ca3bbfb3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,8 +14,9 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-list-endpoints": "^6.0.0", + "express-rate-limit": "^8.5.2", "helmet": "^7.1.0", - "jsonwebtoken": "8.5.1", + "jsonwebtoken": "9.0.0", "mongoose": "^8.4.0", "nodemon": "^3.1.3" } diff --git a/backend/server.js b/backend/server.js index c8d0c218..adda1d70 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,27 +5,86 @@ import express from "express" import mongoose from "mongoose" import bcrypt from "bcrypt" import jwt from "jsonwebtoken" +import rateLimit from "express-rate-limit" // [KRAV K5] Nytt paket: npm install express-rate-limit import { Message } from "./models/Message.js" import { User } from "./models/User.js" import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" -import listEndpoints from "express-list-endpoints" +// [KRAV K4] Borttagen: import listEndpoints from "express-list-endpoints" +// Motivering: Exponerade alla API-endpoints för angripare. Borttagen för att minska attackytan. + +// [KRAV K4] JWT_SECRET hämtas från .env, aldrig hårdkodat i koden. +// Om .env saknas kraschar servern direkt istället för att köra med en osäker konfiguration. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") const PORT = process.env.PORT || "3000" const app = express() app.use(helmet()) + +// [KRAV K4] CORS — Skyddade hemligheter & säker konfiguration +// FÖRE: cors({ origin: "*" }) tillät alla domäner att anropa vårt API. +// EFTER: Begränsat till specifika origins via miljövariabel CORS_ORIGIN. +// Motivering (OWASP A02 Security Misconfiguration): Wildcard-CORS låter skadliga webbsidor göra API-anrop i inloggade användares namn. + +const allowedOrigins = process.env.CORS_ORIGIN + ? process.env.CORS_ORIGIN.split(",") + : ["http://localhost:5173"] + app.use(cors({ - origin: "*", + origin: allowedOrigins, + credentials: true, })) + app.use(express.json()) +// [KRAV K5] Rate Limiting — Hastighetsbegränsning (STRIDE: Denial of Service) +// OWASP A07: Authentication Failures +// +// FÖRE: Ingen begränsning alls — en angripare kunde skicka obegränsat antal anrop och antingen brute force:a lösenord eller överbelasta servern. +// EFTER: Två nivåer: +// - Generell: max 100 anrop per 15 min (skyddar hela API:et) +// - Auth-specifik: max 10 anrop per 15 min på /login och /register (stoppar brute force-attacker mot inloggningen) + +const generalLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 100, + message: { success: false, message: "Too many requests, please try again later" }, +}) + +const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, + message: { success: false, message: "Too many login attempts, please try again later" }, +}) + +app.use(generalLimiter) + +// [KRAV K4] Borttagen: app.get("/", (req, res) => res.send(listEndpoints(app))) +// Motivering: Rot-endpointen listade alla API-endpoints, en komplett attackkarta. app.get("/", (req, res) => { - res.send(listEndpoints(app)) + res.json({ message: "YH Message App API" }) }) -app.post("/register", async (req, res) => { +// [KRAV K1] Spoofing: Lösenordslös inloggning (Passkeys / WebAuthn) +// OWASP A07: Authentication Failures + +// NULÄGE: Appen använder lösenordsbaserad autentisering med bcrypt + JWT. +// Lösenord kan phishas, brute force:as eller läcka via dataintrång. + +// FRAMTIDA IMPLEMENTATION: +// - Installera paketet @simplewebauthn/server +// - Skapa nya endpoints: POST /auth/passkey/register och POST /auth/passkey/login +// - Spara den publika nyckeln (credentialPublicKey) i User-modellen +// - Passkeys är kryptografiskt bundna till domänen +// - Befintlig /register och /login behålls som fallback under övergångsperioden + +// Varför vi inte implementerar det nu: WebAuthn kräver HTTPS i produktion och en omstrukturering av hela auth-flödet (frontend + backend). +// Kravet är formulerat och redo att implementeras i en framtida sprint. + + +app.post("/register", authLimiter, async (req, res) => { + // [KRAV K5] authLimiter tillagd — max 10 registreringsförsök per 15 min per IP try { const { email, password, username } = req.body @@ -65,25 +124,39 @@ app.post("/register", async (req, res) => { }, }) } catch (error) { + // [KRAV K4] FÖRE: error: error — skickade hela Mongoose-felobjektet till klienten. + // EFTER: Generiskt meddelande till klienten, detaljer loggas server-side. + // Motivering: Felobjektet kan avslöja databasstruktur och interna detaljer. + console.error("Register error:", error) res.status(400).json({ success: false, message: "Could not create user", - error: error, }) } }) -app.post("/login", async (req, res) => { +app.post("/login", authLimiter, async (req, res) => { + // [KRAV K5] authLimiter tillagd — max 10 inloggningsförsök per 15 min per IP try { const { login, password } = req.body const user = await User.findOne({ $or: [{ username: login }, { email: login }] }) + // [KRAV K6 / K1] Säkerhetsförbättring: Generiska felmeddelanden + // STRIDE: Spoofing (förhindrar user enumeration) + // + // FÖRE: Två olika meddelanden: + // "No account found with that username or email" (kontot finns inte) + // "Password is incorrect" (kontot finns men lösenordet är fel) + // PROBLEM: En angripare kan kartlägga vilka konton som existerar genom + // att testa användarnamn och se vilket meddelande som returneras. + // EFTER: Samma meddelande i båda fallen. + if (!user) { return res.status(401).json({ success: false, - message: "No account found with that username or email", + message: "Invalid username or password", response: null, }) } @@ -92,7 +165,7 @@ app.post("/login", async (req, res) => { if (!passwordMatch) { return res.status(401).json({ success: false, - message: "Password is incorrect", + message: "Invalid username or password", // Samma meddelande som ovan response: null, }) } @@ -113,10 +186,10 @@ app.post("/login", async (req, res) => { }, }) } catch (error) { + console.error("Login error:", error) res.status(500).json({ success: false, message: "Something went wrong", - error: error, }) } }) @@ -137,10 +210,15 @@ app.get("/messages", async (req, res) => { }) app.post("/messages", authenticateUser, async (req, res) => { - const message = new Message({ message: req.body.message, user: req.user._id }) + // [KRAV K2] Indatavalidering sker via Mongoose-schemat (se models/Message.js) + // som kräver minlength: 3, maxlength: 140, trim: true. + // Vi trimmar även här för konsekvent hantering. + const trimmedMessage = req.body.message?.trim() + const message = new Message({ message: trimmedMessage, user: req.user._id }) try { const saved = await message.save() - res.status(201).json(saved) + const populated = await saved.populate("user", "username") + res.status(201).json(populated) } catch (err) { res.status(400).json({ message: "Could not save message", errors: err.errors }) } @@ -152,25 +230,69 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { const message = await Message.findById(req.params.id) if (!message) return res.status(404).json({ error: "Message not found" }) + // [KRAV K6] Auktoriseringskontroll — ägarkontroll (STRIDE: Elevation of Privilege) + // OWASP A01: Broken Access Control + // + // Denna kontroll fanns redan i PATCH. Vi jämför meddelandets user-fält + // (author_id) mot den inloggade användarens ID från JWT-token. + // Om de inte matchar → 403 Forbidden. Frontenden kan inte kringgå detta. + if (message.user.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "You can only edit your own messages" }) } - message.message = req.body.editedMessage - await message.save() + // [KRAV K2] Konsekvent fältnamn: "message" istället för "editedMessage" + message.message = req.body.message?.trim() + await message.save() // Mongoose-validering (minlength/maxlength) körs här const updated = await message.populate("user", "username") + + // [KRAV K3] Granskningslogg — logga ändringen + console.log(JSON.stringify({ + event: "MESSAGE_UPDATED", + userId: req.user._id, + messageId: message._id, + timestamp: new Date().toISOString(), + })) + res.json(updated) } catch (error) { res.status(400).json({ error: "Could not update message" }) } }) -app.delete("/messages/:id", async (req, res) => { +// [KRAV K6] DELETE - Kritisk säkerhetsfix (STRIDE: Elevation of Privilege) +// OWASP A01: Broken Access Control + +// FÖRE: app.delete("/messages/:id", async (req, res) => { ... }) +// - Ingen authenticateUser-middleware → vem som helst kunde anropa endpointen +// - Ingen ägarkontroll → vilkens meddelande som helst kunde raderas +// - En angripare behövde bara ett meddelande-ID och ett curl-kommando +// +// EFTER: authenticateUser tillagd + ägarkontroll som jämför message.user mot req.user._id (samma mönster som PATCH redan använde) + +app.delete("/messages/:id", authenticateUser, async (req, res) => { if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" }) try { const message = await Message.findById(req.params.id) if (!message) return res.status(404).json({ error: "Message not found" }) + + // [KRAV K6] Ägarkontroll — samma mönster som PATCH + if (message.user.toString() !== req.user._id.toString()) { + return res.status(403).json({ error: "You can only delete your own messages" }) + } + await message.deleteOne() + + // [KRAV K3] Granskningslogg — logga raderingen med tidsstämpel och användar-ID + // STRIDE: Repudiation — utan denna logg kan en användare förneka att de raderat data + // OWASP A09: Security Logging & Monitoring Failures + console.log(JSON.stringify({ + event: "MESSAGE_DELETED", + userId: req.user._id, + messageId: req.params.id, + timestamp: new Date().toISOString(), + })) + res.status(204).send() } catch (error) { res.status(400).json({ error: "Could not delete message" }) @@ -179,4 +301,4 @@ app.delete("/messages/:id", async (req, res) => { app.listen(PORT, () => { console.log(`Listening on port ${PORT}`) -}) +}) \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e43d824e..2210c7f2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,7 +20,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.5" + "vite": "^8.0.16" } }, "node_modules/@babel/code-frame": { @@ -314,378 +314,38 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -839,6 +499,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -860,21 +539,288 @@ "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/pluginutils": { @@ -884,6 +830,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1572,6 +1529,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1784,44 +1751,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2096,6 +2025,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3087,6 +3034,267 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3457,6 +3665,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -3697,22 +3918,46 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rollup": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", - "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" - } + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -4141,6 +4386,31 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4306,50 +4576,66 @@ } }, "node_modules/vite": { - "version": "4.5.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", - "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -4358,6 +4644,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, diff --git a/frontend/package.json b/frontend/package.json index fc417bb3..c6da3d52 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,6 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.5" + "vite": "^8.0.16" } } \ No newline at end of file diff --git a/frontend/src/components/AuthModal.jsx b/frontend/src/components/AuthModal.jsx index 3d25f46b..f3f656c4 100644 --- a/frontend/src/components/AuthModal.jsx +++ b/frontend/src/components/AuthModal.jsx @@ -29,11 +29,11 @@ export const AuthModal = ({ mode, onClose, onSuccess }) => { const data = await res.json() if (!res.ok) throw new Error(data.message || "Something went wrong") - console.log("Auth successful:", data) + // [KRAV K4] Borttagen: console.log("Auth successful:", data) + // Motivering: Loggrade hela auth-svaret inklusive JWT-token till konsolen. onSuccess(data) - } catch (err) { - console.log(err) - setError(err.message) + } catch (error) { + setError(error.message) } finally { setSubmitting(false) } diff --git a/frontend/src/components/PostMessage.jsx b/frontend/src/components/PostMessage.jsx index ba71b001..3102d5b6 100644 --- a/frontend/src/components/PostMessage.jsx +++ b/frontend/src/components/PostMessage.jsx @@ -20,7 +20,9 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => body: JSON.stringify({ message: newPost }), }) - console.log("Token being sent:", user?.response?.accessToken) + // [KRAV K4] Borttagen: console.log("Token being sent:", user?.response?.accessToken) + // Motivering: JWT-token skrevs ut i klartext i webbläsarens konsol. + // Vem som helst med DevTools öppna kunde kopiera token och agera som den inloggade användaren. Känslig data ska ALDRIG loggas i frontend. if (res.status === 401) { onUnauthorized() @@ -31,7 +33,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => const data = await res.json() if (data.message && !data._id) { - console.log(data) + // [KRAV K4] Borttagen: console.log(data): läckte serverdata setErrorMessage(data.message) setSubmitting(false) return @@ -65,6 +67,10 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => setNewPost(e.target.value) setErrorMessage("") }} + // [KRAV K2] Frontend-validering som komplement till backend. + // maxLength hindrar användaren från att skriva mer än 140 tecken. + // OBS: Detta är INTE säkerhetsskyddet, det sitter i Mongoose-schemat. + maxLength={140} />

{errorMessage}

+ {/* + [KRAV K6] Delete-knappen villkorad med isOwner + FÖRE: + - Synlig för ALLA användare, oavsett om de ägde meddelandet + EFTER: Visas bara om isOwner === true + - Samma mönster som edit-knappen redan använde + OBS: Detta är bara UI-skydd. Det riktiga skyddet sitter i backend (authenticateUser + ägarkontroll). + En angripare kan fortfarande skicka ett DELETE-anrop via curl, men backend stoppar det. + */} + {isOwner && ( + + )} {isOwner && !isEditing && ( diff --git a/granskningsfasen.md b/granskningsfasen.md index 088f9718..f4f37a5d 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -1 +1,99 @@ -# Inlämning 3 - Granskningsfasen \ No newline at end of file +# Inlämning 3 - Granskningsfasen[fas 3 - granskning.md](https://github.com/user-attachments/files/29040324/fas.3.-.granskning.md) +## **Fas 3: Granskning** + +--- + +### 1\. Intro och verktyg + +I den här fasen har vi granskat vår webbapp med Dependabot. Det går igenom alla beroenden i projektet, alltså de färdiga kodpaket vi hämtat in utifrån, såsom bcrypt och jsonwebtoken och jämför dem mot en databas med kända sårbarheter. Hittar Dependabot ett paket med en känd brist skapar det en "alert". + +Vi fick 16 alerts. Men de fördelar sig på bara fyra paket, flera alerts är olika varianter av samma grundproblem i samma paket. Så vi har grupperat dem och valt ut det viktigaste att fördjupa oss i. + +--- + +### 2\. Överblick: 16 alerts, fyra paket + +Kort om vad ett "paket" är: när man bygger en webbapp så behöver man inte skriva all kod själv, utan man kan hämta in färdiga moduler via NPM, en pakethanteraren för JavaScript, som en katalog man laddar ner kod ifrån. Varje sån modul är ett paket. Vår app använder många, och det är dom Dependabot har granskat och ger oss olika CVE-koder. + +De 16 alerts:en fördelar sig så här: + +jsonwebtoken: 3st alerts. Paketet som sköter vår inloggning. +Det här är det allvarligaste och det vi valt att gå djupare in på. + +node-tar: 6st alerts, och alla klassade som High. node-tar är ett paket som packar ihop och packar upp filarkiv. +Vi har aldrig installerat det själva; det är ett indirekt beroende, alltså ett paket som något annat paket drar in i bakgrunden. Vår egen kod anropar aldrig det. Det betyder att dom +6 bristerna inte kan utnyttjas mot oss i nuläget \- vi återkommer till varför vi ändå tar dom på allvar. + +Vite, esbuild och launch-editor: har också 6st alerts. Det här är utvecklingsverktyg \- ett program vi bara använder lokalt när vi bygger appen. Vite och esbuild bygger ihop koden, launch-editor öppnar filer i editorn. Alerts:en är markerade som "Development", vilket betyder att de bara berör utvecklingsläget. I produktion byggs appen om till vanliga statiska filer och dom här verktygen körs inte, så slutanvändaren möts aldrig av dom. + +Och qs: har 1 alert. QS är paketet som Express använder för att tolka query-parametrar i en URL, det som står efter frågetecknet i en webbadress. Sårbarheten gör att ett specialformaterat anrop kan överbelasta servern. Men den mildras redan av den rate limit vi byggde i Fas 2, som stoppar en angripare från att skicka tillräckligt många anrop. + +Det är 16st sårbarheter\! Men bara ett paket \- jsonwebtoken \- det påverkar våra användare direkt. Och dit går vi nu. + +--- + +### 3\. Fördjupning: jsonwebtoken + +När du loggar in skapar vår backend en jsonwebtoken åt dig. Det är en liten datasträng som fungerar som ett inloggningsbevis: din webbläsare skickar med den i varje anrop till API:et, så servern vet vem du är utan att du måste logga in på nytt varje gång. + +För att denna token inte ska gå att förfalska signerar servern den med en hemlig nyckel; en kryptografisk signatur som bara vår server kan skapa. När en token kommer in kör backend en verifiering, som kontrollerar att signaturen är giltig. + +Så vad är problemet? + +Problemet sitter i versionen. I vår package.json står "jsonwebtoken": "8.5.1"; en uråldrig version från 2019\. Alla andra paket har ett tak-tecken framför versionsnumret, vilket tillåter automatiska uppdateringar till nyare, kompatibla versioner. Den här raden saknar detta tecken, så paketet är låst och har inte fått några säkerhetsuppdateringar på flera år. Sedan 2019 har tre sårbarheter hittats i just den versionen. + +Den allvarligaste sårbarheten handlar om algoritmkontroll. En jsonwebtoken kan signeras med olika algoritmer. I version 8.5.1 är denna typ av verifiering inte tillräckligt strikt med vilken algoritm den accepterar; den verifierar att signaturen finns och stämmer, men kontrollerar inte hårt att rätt algoritm använts. Det öppnar för en så kallad algorithm confusion-attack, där angriparen med hjälp av en annan algoritm kan tvinga systemet att acceptera en “falsk” token. + +--- + +### + +### 4\. Attacken steg för steg, och OWASP-kopplingen + +För att förstå attacken behöver man veta att det finns två sätt att signera en token. Det ena använder en enda hemlig nyckel, som servern håller för sig själv och använder både till att skapa signaturen och kontrollera den. Det andra använder ett nyckelpar: en privat nyckel som skapar signaturen, och en separat publik nyckel som kontrollerar den. Den publika nyckeln är tänkt att kunna delas öppet, och det är ofarligt, eftersom den bara kan kontrollera signaturer, inte skapa nya. + +Och det är där bristen kommer in. Den gamla versionen av jwt-token kontrollerar inte tillräckligt noga vilket av de här sätten som använts. + +Så en angripare kan skapa en egen token och skriva in en annan användares ID i den. + +De signerar tokenen med den publika nyckeln och lurar servern att behandla den som om det vore den hemliga nyckeln. Och den gamla versionen märker inte skillnaden. + +Sen skickar de tokenen till vårt API. JWT-verifieringen godkänner den. + +Resultatet är att angriparen är inloggad som någon annan, utan att någonsin ha vetat deras lösenord. + +Den här attacken förutsätter att servern använder nyckelpar-metoden, med en publik nyckel. Vår app gör inte det, vi använder endast en hemlig nyckel, så ingen publik nyckel finns att missbruka. Men ett autentiseringspaket med tre kända brister är en risk oavsett. \- och nästa ändringar i koden kan göra den nåbar.. + +I STRIDE är det här Spoofing. I OWASP mappar det mot A03, Software Supply Chain Failures. Det handlar om att sårbarheter kommer in via tredjepartskod man inte skrivit själv. Men vi bär ansvaret för det ändå. Och det är poängen med kategorin: för allt som körs i din app är ditt ansvar, även det du lånat in. + +--- + +### 5\. Åtgärden \+ övriga fynd + +Åtgärden är en rad. + +Vi ändrar "jsonwebtoken": "8.5.1" till "^9.0.0" och kör npm install. Version 9 åtgärdar alla tre sårbarheterna och kräver dessutom att man explicit anger vilken algoritm som ska accepteras, vilket stänger algorithm confusion-attacken. Och tak-tecken framför versionsnumret gör att paketet får framtida säkerhetsuppdateringar automatiskt, så samma misstag inte upprepas. + +Kort om resten: + +node-tar: alla sex är path traversal. Det innebär att en angripare manipulerar en filsökväg för att skriva en fil utanför den avsedda mappen, till exempel skriva över en känslig fil genom att döpa en fil i arkivet till något som bla bla../../.env. Eftersom vår app aldrig packar upp arkiv går det inte att utnyttja idag. Men vi åtgärdar det ändå för att minska attack-ytan, skulle appen byggas ut, eller en angripare ta sig in en annan väg, ska det inte ligga kvar. Det fixas via versions-styrning av paketet (i vår branch med ett overrides-block, alternativt npm audit fix). + +Vite-gruppen: åtgärdas på samma sätt och berör ändå bara utvecklingsläget. + +QS: löser sig genom att uppdatera Express, som då drar in en patchad version. + +--- + +### 6\. Slutsats + +Sammanfattningsvis: 16 alerts, fyra paket, och det viktigaste fyndet löstes med en liten ändring. + +En viktig poäng att notera här: Dependabot granskar bara beroenden, inte vår egen kod. Vår egen kod granskade vi själva i Fas 2: autentisering på DELETE, ägarkontroll, indatavalidering och rate limiting. De två granskningarna täcker olika saker; vi tog koden vi skrivit, Dependabot tog koden vi lånat in. Tillsammans täcker de hela appen. + +länk till vår presentation: [FAS3.pdf](https://github.com/user-attachments/files/29040497/FAS3.pdf) + +{933BD7C2-423C-415F-96D2-B434713388A5} + +image + +