From bf1c95da775721cd3c6caf330083035add353bfb Mon Sep 17 00:00:00 2001 From: karinisaksson <152193321+karinisaksson@users.noreply.github.com> Date: Wed, 27 May 2026 13:47:42 +0200 Subject: [PATCH 01/40] Insert image in planeringsfasen.md Added an image to the planning phase document. --- planeringsfasen.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/planeringsfasen.md b/planeringsfasen.md index 661cae0e..3648951a 100644 --- a/planeringsfasen.md +++ b/planeringsfasen.md @@ -1 +1,3 @@ -# Inlämning 1 - Planeringsfasen \ No newline at end of file +# Inlämning 1 - Planeringsfasen + +Hotmodellering From cc0ab503721d70687fbc465ce8d53b268813b43c Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 27 May 2026 14:13:43 +0200 Subject: [PATCH 02/40] =?UTF-8?q?S=C3=A4kerhetsrisker=20och=20krav=20tilla?= =?UTF-8?q?gda?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- planeringsfasen.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/planeringsfasen.md b/planeringsfasen.md index 3648951a..9a1cf520 100644 --- a/planeringsfasen.md +++ b/planeringsfasen.md @@ -1,3 +1,25 @@ # Inlämning 1 - Planeringsfasen Hotmodellering + +Säkerhetsrisker: +1. En angripare kan skriva in skadliga databaskommandon via formulär och på så sätt logga in som andra användare, få tillgång till data i databasen eller radera all data. (E, D, S) +2. En användare kan nå meddelandesidan och skriva meddelanden utan att vara inloggad, genom att skriva in URL:en direkt i webbläsaren. (E) +3. En inloggad användare kan redigera någon annans meddelande genom att manipulera id-numret i anropet. (E, T) +4. En angripare som kommer åt databasen hittar lösenord sparade i klartext. (I) +5. En angripare kan använda brute force för att logga in som en annan användare. (S) +6. En angripare kan kapa ett konto via lösenordsåterställning. (S, E) +7. En angripare kan avlyssna och ändra data som är i transit mellan systemets delar. (T, I) +8. En användare kan skapa, redigera eller radera ett meddelande och förneka att de gjorde det. (R) +9. En användare kan skicka extremt långa meddelanden som överbelastar servern. (D) + +Säkerhetskrav: +1. Backend ska validera och rensa input, samt säkert hantera input från användare så att det inte kan tolkas som databaskommandon (t.ex parameterized queries för sql-databas, eller sanitering av input for MongoDB) +2. Systemet ska kräva inloggning för att en användare ska kunna se, skriva eller redigera meddelanden. +3. Innan redigering eller radering av ett meddelande ska systemet kontrollera att meddelandets id är kopplat till den inloggade användaren. +4. Lösenord ska aldrig lagras i klartext i databasen. +5. Systemet ska blockera inloggning efter 5 misslyckade försök. +6. För att återställa lösenord krävs en engångskod skickad till användarens e-post. +7. All kommunikation ska vara krypterad via HTTPS och TLS. +8. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. +9. Ett meddelande ska vara minst 3 och max 140 tecken. From 1bf2ba29f18a64d028d6eea56b2d35a1f27239da Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 28 May 2026 10:14:38 +0200 Subject: [PATCH 03/40] La till en som jag missat --- planeringsfasen.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/planeringsfasen.md b/planeringsfasen.md index 9a1cf520..1e453e02 100644 --- a/planeringsfasen.md +++ b/planeringsfasen.md @@ -4,22 +4,24 @@ Säkerhetsrisker: 1. En angripare kan skriva in skadliga databaskommandon via formulär och på så sätt logga in som andra användare, få tillgång till data i databasen eller radera all data. (E, D, S) -2. En användare kan nå meddelandesidan och skriva meddelanden utan att vara inloggad, genom att skriva in URL:en direkt i webbläsaren. (E) -3. En inloggad användare kan redigera någon annans meddelande genom att manipulera id-numret i anropet. (E, T) -4. En angripare som kommer åt databasen hittar lösenord sparade i klartext. (I) -5. En angripare kan använda brute force för att logga in som en annan användare. (S) -6. En angripare kan kapa ett konto via lösenordsåterställning. (S, E) -7. En angripare kan avlyssna och ändra data som är i transit mellan systemets delar. (T, I) -8. En användare kan skapa, redigera eller radera ett meddelande och förneka att de gjorde det. (R) -9. En användare kan skicka extremt långa meddelanden som överbelastar servern. (D) +2. En inloggad användare kan ge sig själv admin-rättigheter. (E) +3. En användare kan nå meddelandesidan och skriva meddelanden utan att vara inloggad, genom att skriva in URL:en direkt i webbläsaren. (E) +4. En inloggad användare kan redigera någon annans meddelande genom att manipulera id-numret i anropet. (E, T) +5. En angripare som kommer åt databasen hittar lösenord sparade i klartext. (I) +6. En angripare kan använda brute force för att logga in som en annan användare. (S) +7. En angripare kan kapa ett konto via lösenordsåterställning. (S, E) +8 En angripare kan avlyssna och ändra data som är i transit mellan systemets delar. (T, I) +9. En användare kan skapa, redigera eller radera ett meddelande och förneka att de gjorde det. (R) +10. En användare kan skicka extremt långa meddelanden som överbelastar servern. (D) Säkerhetskrav: 1. Backend ska validera och rensa input, samt säkert hantera input från användare så att det inte kan tolkas som databaskommandon (t.ex parameterized queries för sql-databas, eller sanitering av input for MongoDB) -2. Systemet ska kräva inloggning för att en användare ska kunna se, skriva eller redigera meddelanden. -3. Innan redigering eller radering av ett meddelande ska systemet kontrollera att meddelandets id är kopplat till den inloggade användaren. -4. Lösenord ska aldrig lagras i klartext i databasen. -5. Systemet ska blockera inloggning efter 5 misslyckade försök. -6. För att återställa lösenord krävs en engångskod skickad till användarens e-post. -7. All kommunikation ska vara krypterad via HTTPS och TLS. -8. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. -9. Ett meddelande ska vara minst 3 och max 140 tecken. +2. Systemet ska inte tillåta användare att själva sätta eller ändra sin roll. +3. Systemet ska kräva inloggning för att en användare ska kunna se, skriva eller redigera meddelanden. +4. Innan redigering eller radering av ett meddelande ska systemet kontrollera att meddelandets id är kopplat till den inloggade användaren. +5. Lösenord ska aldrig lagras i klartext i databasen. +6. Systemet ska blockera inloggning efter 5 misslyckade försök. +7. För att återställa lösenord krävs en engångskod skickad till användarens e-post. +8. All kommunikation ska vara krypterad via HTTPS och TLS. +9. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. +10. Ett meddelande ska vara minst 3 och max 140 tecken. From b5fa02b7314e711d352c9e3e2beafe695fe8333c Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 28 May 2026 10:14:53 +0200 Subject: [PATCH 04/40] La till en som jag missat --- planeringsfasen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planeringsfasen.md b/planeringsfasen.md index 1e453e02..17aa5425 100644 --- a/planeringsfasen.md +++ b/planeringsfasen.md @@ -16,7 +16,7 @@ Säkerhetsrisker: Säkerhetskrav: 1. Backend ska validera och rensa input, samt säkert hantera input från användare så att det inte kan tolkas som databaskommandon (t.ex parameterized queries för sql-databas, eller sanitering av input for MongoDB) -2. Systemet ska inte tillåta användare att själva sätta eller ändra sin roll. +2. Systemet ska inte tillåta användare att själva sätta eller ändra sin roll. 3. Systemet ska kräva inloggning för att en användare ska kunna se, skriva eller redigera meddelanden. 4. Innan redigering eller radering av ett meddelande ska systemet kontrollera att meddelandets id är kopplat till den inloggade användaren. 5. Lösenord ska aldrig lagras i klartext i databasen. From ff8bea874fbfc6bad7ebcbd5cb8a0fed1e9a14ac Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 2 Jun 2026 11:10:44 +0200 Subject: [PATCH 05/40] =?UTF-8?q?kommentarer=20till=20s=C3=A4kerhetskrav?= =?UTF-8?q?=203,=204=20och=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/models/Message.js | 7 ++- backend/server.js | 19 +++++- frontend/src/App.jsx | 76 ++++++++++++++--------- frontend/src/components/PostMessage.jsx | 7 +++ frontend/src/components/SingleMessage.jsx | 10 ++- frontend/src/index.css | 2 +- 6 files changed, 83 insertions(+), 38 deletions(-) diff --git a/backend/models/Message.js b/backend/models/Message.js index dbca0001..3708e457 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -1,15 +1,18 @@ import mongoose from "mongoose" +// för att definiera hur lång meddelandet får vara, skriv in +// minlength: 3, +// maxlength: 140 (efter required:true) const messageSchema = new mongoose.Schema({ message: { type: String, - required: true + required: true, }, user: { type: mongoose.Schema.Types.ObjectId, ref: "User", }, -createdAt: { + createdAt: { type: Date, default: Date.now, }, diff --git a/backend/server.js b/backend/server.js index c8d0c218..10194b2c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -17,7 +17,9 @@ const PORT = process.env.PORT || "3000" const app = express() app.use(helmet()) app.use(cors({ - origin: "*", + origin: "*", // vilken webbsida som helst på internet kan skicka requests till min API. kan blir ett säkerhetsproblem. tillåter alla domäner att skicka requests till API:et, vilket är dålig praxis i produktion men inte direkt kopplat till något av mina säkerhetskrav. + // bör ändras till app.use(cors({origin: min-frontends-url})) + })) app.use(express.json()) @@ -123,6 +125,7 @@ app.post("/login", async (req, res) => { const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) +//lägg till authenticateUser i app.get("/messages") så att endast inloggade användare kan se meddelanden. Det är en del av mitt säkerhetskrav 1, alltså att endast autentiserade användare ska kunna se meddelanden. app.get("/messages", async (req, res) => { try { const messages = await Message.find() @@ -135,7 +138,12 @@ app.get("/messages", async (req, res) => { res.status(500).json({ message: "Could not fetch messages" }) } }) - +// för att validera att meddelandet är mellan 3 och 140 tecken hanterar jag eventuella valideringsfel i app.post efter första måsvingen +// app.post("/messages", authenticateUser, async (req, res) => { +// const text = req.body.message +// if (!text || text.length < 3 || text.length > 140) { +// return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) +// } app.post("/messages", authenticateUser, async (req, res) => { const message = new Message({ message: req.body.message, user: req.user._id }) try { @@ -165,11 +173,18 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { } }) +// här nedan, i app.delete saknas AuthenticateUser, vilket gör att vem som helst kan radera meddelanden utan att vara inloggad. Det är en säkerhetsrisk och bör åtgärdas genom att lägga till authenticateUser middleware i delete-routen. Det löser delvis krav 4, alltså att ägarskap ska kontrolleras innan radering av meddelanden. app.delete("/messages/:id", 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" }) + + // här behövs även läggas till en kontroll för att säkerställa att endast ägaren av meddelandet kan radera det, annars kan vem som helst radera alla meddelanden. Detta gör att ägarskap av meddelande krävs vid radering, alltså mitt säkerhetskrav 4. + //if (message.user.toString() !== req.user._id.toString()) { + // return res.status(403).json({ error: "You can only delete your own messages" }) + //} + await message.deleteOne() res.status(204).send() } catch (error) { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c668778f..a10026a8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,8 +22,12 @@ export const App = () => { useEffect(() => { fetchPosts() }, []) + // denna bör göras om såhär: + //useEffect(() => { + // if (user) fetchPosts() + //}, [user]) - const addNewPost = (newMessage) => { + const addNewPost = (newMessage) => { setMessageList([newMessage, ...messageList]) } @@ -31,43 +35,43 @@ export const App = () => { setUser(null) setError("Your session has expired, please log in again") } - + return ( <> - {user ? ( -
- {user.response.username} - -
- ) : ( -
- - -
- )} + {user ? ( +
+ {user.response.username} + +
+ ) : ( +
+ + +
+ )} {modal && ( setModal(null)} - onSuccess={(data) => { + onSuccess={(data) => { console.log("User logged in:", data) - setUser(data) - setModal(null) + setUser(data) + setModal(null) }} /> )} @@ -80,6 +84,16 @@ export const App = () => { fetchPosts={fetchPosts} user={user} onUnauthorized={handleUnauthorized} + + // för att endast inloggade användare ska kunna se meddelanden bör MessageList inte renderas alls om user är null, alltså om ingen är inloggad. Det är en del av mitt säkerhetskrav 1. + //{user && } /> ) diff --git a/frontend/src/components/PostMessage.jsx b/frontend/src/components/PostMessage.jsx index ba71b001..815ab3d7 100644 --- a/frontend/src/components/PostMessage.jsx +++ b/frontend/src/components/PostMessage.jsx @@ -10,6 +10,13 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => event.preventDefault() setSubmitting(true) + // detta sätts in för att validera i frontenden att meddelandet är mellan 3 och 140 tecken, och att användaren inte kan skicka in ett tomt meddelande. + // if (newPost.length < 3 || newPost.length > 140) { + // setErrorMessage("Message must be between 3 and 140 characters") + // setSubmitting(false) + // return + // } + try { const res = await fetch(`${BASE_URL}/messages`, { method: "POST", diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index 50d2274b..122eea3c 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -28,6 +28,11 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => } } + //lägg här in validering i frontenden att ändringen av meddelandet måste vara mellan 3 och 140 tecken och att användaren inte kan skapa ett tomt meddelande. + // if (editedText.length < 3 || editedText.length > 140) { + // setEditError("Message must be between 3 and 140 characters") + // return + // } const onSave = async () => { try { const res = await fetch(`${BASE_URL}/messages/${message._id}`, { @@ -82,6 +87,8 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => )} + {/* för att endast ägaren av meddelandet ska kunna radera bör isOwner läggas till som villkor på deleteknappen. Då visas den inte för andra användare, vilket är en del av mitt säkerhetskrav 4. */} + {/*{isOwner && }*/}
@@ -102,7 +109,6 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => setEditError("") }} > - ❌ )}
@@ -113,4 +119,4 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => ) -} +} \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 901556d8..d8263b28 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -233,4 +233,4 @@ input { h2 { font-size: 18px; margin: 0 0 5px 0; -} +} \ No newline at end of file From 81eebbe4f48ae07816d6d91a1d8c4cc7f1b6c0f0 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 2 Jun 2026 14:32:15 +0200 Subject: [PATCH 06/40] =?UTF-8?q?l=C3=B6senordshashning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/server.js b/backend/server.js index 10194b2c..700546c0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -46,7 +46,8 @@ app.post("/register", async (req, res) => { message: `A user with this ${field} already exists` }) } - + // här hashas lösenord med bcrypt innan det sparas i databasen, vilket åtgärdar säkerhetsåtgärd 5. + // bcrypt är en envägsfunktion, lösenordet kan inte återskapas från hashen. const hashedPassword = await bcrypt.hash(password, 10) const user = new User({ username: username.trim(), email, password: hashedPassword }) await user.save() @@ -90,6 +91,7 @@ app.post("/login", async (req, res) => { }) } + // här jämförs det angivna lösenordet med det hashade lösenordet i databasen, vilket är en del av säkerhetsåtgärd 5. const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { return res.status(401).json({ From 1591003cc030460f1d166b88eac3d92027bc7d4f Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 2 Jun 2026 17:45:27 +0200 Subject: [PATCH 07/40] =?UTF-8?q?implementerat=20=C3=A4ndringar=20f=C3=B6r?= =?UTF-8?q?=20krav=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 4 ++-- frontend/src/App.jsx | 16 +++++++++------- frontend/src/components/AuthModal.jsx | 4 ++-- frontend/src/components/PostMessage.jsx | 7 ++++--- frontend/src/components/SingleMessage.jsx | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/backend/server.js b/backend/server.js index 700546c0..ed37794f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -127,8 +127,8 @@ app.post("/login", async (req, res) => { const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) -//lägg till authenticateUser i app.get("/messages") så att endast inloggade användare kan se meddelanden. Det är en del av mitt säkerhetskrav 1, alltså att endast autentiserade användare ska kunna se meddelanden. -app.get("/messages", async (req, res) => { +//lagt till authenticateUser i app.get("/messages") så att endast inloggade användare kan se meddelanden. Det är en del av mitt säkerhetskrav 3, alltså att endast inloggade användare ska kunna se meddelanden. +app.get("/messages", authenticateUser, async (req, res) => { try { const messages = await Message.find() .sort({ createdAt: "desc" }) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a10026a8..632d3871 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,13 +19,15 @@ export const App = () => { .catch((error) => console.error(error)) .finally(() => setLoading(false)) } + useEffect(() => { - fetchPosts() - }, []) - // denna bör göras om såhär: + if (user) fetchPosts() + }, [user]) + + // ovan har gjorts om för att endast inloggade användare ska kunna se meddelanden. Nedan är den tidigare versionen där fetchPosts kördes varje gång appen laddades, oavsett om en användare var inloggad eller inte. Det är en del av mitt säkerhetskrav 4, alltså att endast autentiserade användare ska kunna se meddelanden. //useEffect(() => { - // if (user) fetchPosts() - //}, [user]) + // fetchPosts() + // }, []) const addNewPost = (newMessage) => { setMessageList([newMessage, ...messageList]) @@ -69,7 +71,7 @@ export const App = () => { mode={modal} onClose={() => setModal(null)} onSuccess={(data) => { - console.log("User logged in:", data) + // console.log("User logged in:", data) - borttagen för att inte exponera token i konsolen, vilket är del av säkerhetskrav 3. det är lättare att komma över en token som exponeras i konsolen och på så sätt ta sig in i en annans konto och kunna ändra eller ta bort meddelanden. setUser(data) setModal(null) }} @@ -85,7 +87,7 @@ export const App = () => { user={user} onUnauthorized={handleUnauthorized} - // för att endast inloggade användare ska kunna se meddelanden bör MessageList inte renderas alls om user är null, alltså om ingen är inloggad. Det är en del av mitt säkerhetskrav 1. + // för att endast inloggade användare ska kunna se meddelanden bör MessageList inte renderas alls om user är null, alltså om ingen är inloggad. detta kanske inte behövs enligt claude?? //{user && { const data = await res.json() if (!res.ok) throw new Error(data.message || "Something went wrong") - console.log("Auth successful:", data) + // console.log("Auth successful:", data) tog bort console.log för att inte exponera token onSuccess(data) } catch (err) { - console.log(err) + // console.log(err) tog även bort denna för att inte exponera felmeddelanden med känslig information om tex filnamn och radnummer i min kod. setError(err.message) } finally { setSubmitting(false) diff --git a/frontend/src/components/PostMessage.jsx b/frontend/src/components/PostMessage.jsx index 815ab3d7..8c7c6d92 100644 --- a/frontend/src/components/PostMessage.jsx +++ b/frontend/src/components/PostMessage.jsx @@ -27,7 +27,8 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => body: JSON.stringify({ message: newPost }), }) - console.log("Token being sent:", user?.response?.accessToken) + // tar bort denna console.log för att inte token ska kunna ses i konsolen. det är en del av mitt säkerhetskrav 3, eftersom det är lättare att komma över en token som exponeras i konsolen och på så sätt ta sig in i en annans konto och kunna ändra eller ta bort meddelanden. + // console.log("Token being sent:", user?.response?.accessToken) if (res.status === 401) { onUnauthorized() @@ -56,8 +57,8 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => } if (!user) { - return

Log in to write a message

- } + return

Log in to see and write messages

+ } //meddelandet ändrat för att förtydliga att användaren måste logga in för att både se och skriva meddelanden. return (
diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index 122eea3c..50ad9f1a 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -100,7 +100,7 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => )} - {isOwner && isEditing && ( + {isOwner & isEditing && ( }*/} + {/* för att endast ägaren av meddelandet ska kunna radera har isOwner lagts till som villkor på deleteknappen. Då visas den inte för andra användare, vilket är en del av mitt säkerhetskrav 4. */}
- + {isOwner && } {isOwner && !isEditing && ( )} - {isOwner && isEditing && ( + {isOwner && isEditing && ( // “edit”-knappen dyker bara upp för den som är inloggad och äger meddelandet. säkerhetskrav 3 och 4. )} - {isOwner & isEditing && ( + {isOwner && isEditing && ( )}
From 7ec031731eae57f62c1470c96c97c1e269bff520 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 2 Jun 2026 23:05:26 +0200 Subject: [PATCH 09/40] =?UTF-8?q?s=C3=A4kerhetskrav=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/models/Message.js | 6 +++--- backend/server.js | 16 +++++++++------- frontend/src/components/PostMessage.jsx | 14 +++++++------- frontend/src/components/SingleMessage.jsx | 10 +++++----- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/backend/models/Message.js b/backend/models/Message.js index 3708e457..242e9632 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -1,12 +1,12 @@ import mongoose from "mongoose" -// för att definiera hur lång meddelandet får vara, skriv in -// minlength: 3, -// maxlength: 140 (efter required:true) +// hängsle och livrem för att stppa inlägg som är för korta eller för långa const messageSchema = new mongoose.Schema({ message: { type: String, required: true, + minlength: 3, + maxlength: 140 }, user: { type: mongoose.Schema.Types.ObjectId, diff --git a/backend/server.js b/backend/server.js index 8f99af09..395c6aa3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -140,15 +140,12 @@ app.get("/messages", authenticateUser, async (req, res) => { res.status(500).json({ message: "Could not fetch messages" }) } }) -// för att validera att meddelandet är mellan 3 och 140 tecken hanterar jag eventuella valideringsfel i app.post efter första måsvingen -// app.post("/messages", authenticateUser, async (req, res) => { -// const text = req.body.message -// if (!text || text.length < 3 || text.length > 140) { -// return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) -// } - //att authenicateUser finns med i app.post, gör att det krävs en giltig token för att skriva ett meddelande. Detta fanns redan med i koden från början. Uppfyller säkerhetskrav 3. app.post("/messages", authenticateUser, async (req, res) => { + const text = req.body.message + if (!text || text.length < 3 || text.length > 140) { + return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // detta är en del av säkerhetskrav 10, begränsing av längd på meddelanden. + } const message = new Message({ message: req.body.message, user: req.user._id }) try { const saved = await message.save() @@ -169,6 +166,11 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { if (message.user.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "You can only edit your own messages" }) } + // här lägger jag till en kontroll för att säkerställa att meddelandet är mellan 3 och 140 tecken, annars kan användare skicka in väldigt långa eller väldigt korta meddelanden. Detta är en del av säkerhetskrav 10. + const editedText = req.body.editedMessage + if (!editedText || editedText.length < 3 || editedText.length > 140) { + return res.status(400).json({ error: "Message must be between 3 and 140 characters" }) + } message.message = req.body.editedMessage await message.save() diff --git a/frontend/src/components/PostMessage.jsx b/frontend/src/components/PostMessage.jsx index f66b3752..4345e5db 100644 --- a/frontend/src/components/PostMessage.jsx +++ b/frontend/src/components/PostMessage.jsx @@ -10,12 +10,12 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => event.preventDefault() setSubmitting(true) - // detta sätts in för att validera i frontenden att meddelandet är mellan 3 och 140 tecken, och att användaren inte kan skicka in ett tomt meddelande. - // if (newPost.length < 3 || newPost.length > 140) { - // setErrorMessage("Message must be between 3 and 140 characters") - // setSubmitting(false) - // return - // } + // detta sätts in för att validera i frontenden att meddelandet som postas är mellan 3 och 140 tecken, säkerhetskrav 10. + if (newPost.length < 3 || newPost.length > 140) { + setErrorMessage("Message must be between 3 and 140 characters") + setSubmitting(false) + return + } try { const res = await fetch(`${BASE_URL}/messages`, { @@ -58,7 +58,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => if (!user) { return

Log in to see and write messages

- } // Om man inte är inloggad renderas inte formuläret för att skriva meddelanden. Meddelandet ändrat för att förtydliga att användaren måste logga in för att både se och skriva meddelanden. + } // Om man inte är inloggad renderas inte formuläret för att skriva meddelanden. Meddelandet ändrat för att förtydliga att användaren måste logga in för att både se och skriva meddelanden. säkerhetskrav 3. return (
diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index b0a053e2..2260e227 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -28,11 +28,11 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => } } - //lägg här in validering i frontenden att ändringen av meddelandet måste vara mellan 3 och 140 tecken och att användaren inte kan skapa ett tomt meddelande. - // if (editedText.length < 3 || editedText.length > 140) { - // setEditError("Message must be between 3 and 140 characters") - // return - // } + //lägger här in validering i frontenden att meddelande som redigeras måste vara mellan 3 och 140 tecken. säkerhetskrav 10. + if (editedText.length < 3 || editedText.length > 140) { + setEditError("Message must be between 3 and 140 characters") + return + } const onSave = async () => { try { const res = await fetch(`${BASE_URL}/messages/${message._id}`, { From 49547001a36480883489d1896582fa8acaac0b55 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 2 Jun 2026 23:12:44 +0200 Subject: [PATCH 10/40] =?UTF-8?q?fix=20s=C3=A4kerhetskrav=2010?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/SingleMessage.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index 2260e227..fc7f0b87 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -28,12 +28,12 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => } } - //lägger här in validering i frontenden att meddelande som redigeras måste vara mellan 3 och 140 tecken. säkerhetskrav 10. - if (editedText.length < 3 || editedText.length > 140) { - setEditError("Message must be between 3 and 140 characters") - return - } const onSave = async () => { + //lägger här in validering i frontenden att meddelande som redigeras måste vara mellan 3 och 140 tecken. säkerhetskrav 10. + if (editedText.length < 3 || editedText.length > 140) { + setEditError("Message must be between 3 and 140 characters") + return + } try { const res = await fetch(`${BASE_URL}/messages/${message._id}`, { method: "PATCH", From f41cfb227874007944b3d26cab2798e4c99171c9 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 3 Jun 2026 11:42:42 +0200 Subject: [PATCH 11/40] =?UTF-8?q?s=C3=A4kerhetskrav=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/models/Message.js | 2 +- backend/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/models/Message.js b/backend/models/Message.js index 242e9632..d2259636 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -1,6 +1,6 @@ import mongoose from "mongoose" -// hängsle och livrem för att stppa inlägg som är för korta eller för långa +// hängsle och livrem för att stoppa att databasen sparar inlägg som är för korta eller för långa const messageSchema = new mongoose.Schema({ message: { type: String, diff --git a/backend/server.js b/backend/server.js index 395c6aa3..30ade267 100644 --- a/backend/server.js +++ b/backend/server.js @@ -91,7 +91,7 @@ app.post("/login", async (req, res) => { }) } - // här jämförs det angivna lösenordet med det hashade lösenordet i databasen, vilket är en del av säkerhetsåtgärd 5. + // här jämförs det angivna lösenordet som hashas med det hashade lösenordet i databasen, vilket är en del av säkerhetsåtgärd 5. const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { return res.status(401).json({ From ed47d0585acf42b96e9f96f2d5be5521c8347a2a Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 3 Jun 2026 12:16:04 +0200 Subject: [PATCH 12/40] =?UTF-8?q?s=C3=A4kerhetskrav=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 28 ++++++++++++++++++++++++++++ backend/package.json | 1 + backend/server.js | 10 +++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index d8c4b3cd..be5649e9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "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", "mongoose": "^8.4.0", @@ -619,6 +620,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 +1009,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", diff --git a/backend/package.json b/backend/package.json index 1f47ffb6..a3a47f41 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,6 +14,7 @@ "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", "mongoose": "^8.4.0", diff --git a/backend/server.js b/backend/server.js index 30ade267..b6ab8749 100644 --- a/backend/server.js +++ b/backend/server.js @@ -10,6 +10,7 @@ import { User } from "./models/User.js" import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" import listEndpoints from "express-list-endpoints" +import rateLimit from "express-rate-limit" // importerar express-rate-limit för att implementera rate limiting, vilket är en del av säkerhetskrav 6. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -22,6 +23,12 @@ app.use(cors({ })) app.use(express.json()) +const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, + message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } +}) +// här har jag lagt in en variabel LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. app.get("/", (req, res) => { res.send(listEndpoints(app)) @@ -76,7 +83,8 @@ app.post("/register", async (req, res) => { } }) -app.post("/login", async (req, res) => { +//lagt till middlewaren loginLimiter för att begränsa antalet inloggningsförsök, vilket är en del av säkerhetskrav 6. +app.post("/login", loginLimiter, async (req, res) => { try { const { login, password } = req.body const user = await User.findOne({ From 9b025a64c7d71e0ccd0d06018f130e2b4a488d8e Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 3 Jun 2026 15:04:40 +0200 Subject: [PATCH 13/40] =?UTF-8?q?=C3=A4ndrade=20till=20min=20egna=20databa?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/api.js b/frontend/src/api.js index ee6f3531..73caeaf6 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1 +1,4 @@ -export const BASE_URL = "https://yh-message-app-fullstack.onrender.com" +export const BASE_URL = "http://localhost:3000" + +//export const BASE_URL = "https://yh-message-app-fullstack.onrender.com" +//den gamla! \ No newline at end of file From 8a910e7c55ecb12486d71531d79f40282469271c Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Fri, 5 Jun 2026 12:42:23 +0200 Subject: [PATCH 14/40] =?UTF-8?q?s=C3=A4kerhetskrav=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 632d3871..4bc62565 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,9 +11,23 @@ export const App = () => { const [modal, setModal] = useState(null) const [error, setError] = useState(null) + // const fetchPosts = () => { + // setLoading(true) + // fetch(`${BASE_URL}/messages`) + // .then((res) => res.json()) + // .then((data) => setMessageList(data)) + // .catch((error) => console.error(error)) + // .finally(() => setLoading(false)) + // } + + //nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar nu med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. const fetchPosts = () => { setLoading(true) - fetch(`${BASE_URL}/messages`) + fetch(`${BASE_URL}/messages`, { + headers: { + Authorization: `Bearer ${user?.response?.accessToken}`, + }, + }) .then((res) => res.json()) .then((data) => setMessageList(data)) .catch((error) => console.error(error)) From bb1fb62fb094b5c6b66c7bdcc05b448a3267e933 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Fri, 5 Jun 2026 13:14:45 +0200 Subject: [PATCH 15/40] =?UTF-8?q?s=C3=A4klerhetskrav=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 10 ++++++++++ backend/package.json | 1 + backend/server.js | 2 ++ 3 files changed, 13 insertions(+) diff --git a/backend/package-lock.json b/backend/package-lock.json index be5649e9..90e93b82 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-list-endpoints": "^6.0.0", + "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^8.5.2", "helmet": "^7.1.0", "jsonwebtoken": "8.5.1", @@ -620,6 +621,15 @@ "node": ">=10" } }, + "node_modules/express-mongo-sanitize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.2.0.tgz", + "integrity": "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ==", + "license": "MIT", + "engines": { + "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", diff --git a/backend/package.json b/backend/package.json index a3a47f41..1c17c054 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,6 +14,7 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "express-list-endpoints": "^6.0.0", + "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^8.5.2", "helmet": "^7.1.0", "jsonwebtoken": "8.5.1", diff --git a/backend/server.js b/backend/server.js index b6ab8749..806e8203 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,6 +11,7 @@ import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" import listEndpoints from "express-list-endpoints" import rateLimit from "express-rate-limit" // importerar express-rate-limit för att implementera rate limiting, vilket är en del av säkerhetskrav 6. +import mongoSanitize from "express-mongo-sanitize" //importerar för att sanera input och skydda mot NoSQL-injection, vilket är en del av säkerhetskrav 1. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -23,6 +24,7 @@ app.use(cors({ })) app.use(express.json()) +app.use(mongoSanitize()) // här används mongoSanitize middleware för att sanera input och skydda mot NoSQL-injection const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, From 88b4a5987cdfa9458b0eb4866a1c0c37d7fbcba4 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Fri, 5 Jun 2026 14:57:50 +0200 Subject: [PATCH 16/40] =?UTF-8?q?uppdaterade=20s=C3=A4kerhetskrav=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/rateLimiter.js | 9 +++++++++ backend/server.js | 11 +++-------- 2 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 backend/middleware/rateLimiter.js diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js new file mode 100644 index 00000000..45d8eef6 --- /dev/null +++ b/backend/middleware/rateLimiter.js @@ -0,0 +1,9 @@ +import rateLimit from "express-rate-limit" + +export const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, + message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } +}) + +// här har jag lagt in en variabel LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 806e8203..bb326e74 100644 --- a/backend/server.js +++ b/backend/server.js @@ -10,7 +10,8 @@ import { User } from "./models/User.js" import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" import listEndpoints from "express-list-endpoints" -import rateLimit from "express-rate-limit" // importerar express-rate-limit för att implementera rate limiting, vilket är en del av säkerhetskrav 6. +import { loginLimiter } from "./middleware/rateLimiter.js" +// importerar middlewaren loginLimiter, vilket är en del av säkerhetskrav 6. import mongoSanitize from "express-mongo-sanitize" //importerar för att sanera input och skydda mot NoSQL-injection, vilket är en del av säkerhetskrav 1. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -24,13 +25,7 @@ app.use(cors({ })) app.use(express.json()) -app.use(mongoSanitize()) // här används mongoSanitize middleware för att sanera input och skydda mot NoSQL-injection -const loginLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 5, - message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } -}) -// här har jag lagt in en variabel LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. +app.use(mongoSanitize()) // här används mongoSanitize för att sanera input och skydda mot NoSQL-injection app.get("/", (req, res) => { res.send(listEndpoints(app)) From a877d91c0afb7934db1d77c7abccc692d904d500 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Fri, 5 Jun 2026 16:34:10 +0200 Subject: [PATCH 17/40] =?UTF-8?q?la=20till=20console.log=20f=C3=B6r=20att?= =?UTF-8?q?=20testa=20=C3=A4garskapskontroll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/server.js b/backend/server.js index bb326e74..9c794bf5 100644 --- a/backend/server.js +++ b/backend/server.js @@ -195,6 +195,7 @@ app.delete("/messages/:id", authenticateUser, async (req, res) => { // här lägger jag till en kontroll för att säkerställa att endast ägaren av meddelandet kan radera det, annars kan vem som helst radera alla meddelanden. Säkerhetskrav 4. if (message.user.toString() !== req.user._id.toString()) { + console.log(`BLOCKED: User ${req.user._id} tried to delete message owned by ${message.user}`) return res.status(403).json({ error: "You can only delete your own messages" }) } From 6f2c491e60053495ad1c4257af067b4d4c9e7e0b Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Sun, 7 Jun 2026 22:49:56 +0200 Subject: [PATCH 18/40] =?UTF-8?q?F=C3=B6rtydliganden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/rateLimiter.js | 2 +- backend/server.js | 8 ++++---- frontend/src/components/SingleMessage.jsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 45d8eef6..4ffacff4 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -6,4 +6,4 @@ export const loginLimiter = rateLimit({ message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } }) -// här har jag lagt in en variabel LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file +// här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 9c794bf5..af8ece1a 100644 --- a/backend/server.js +++ b/backend/server.js @@ -20,12 +20,12 @@ const PORT = process.env.PORT || "3000" const app = express() app.use(helmet()) app.use(cors({ - origin: "*", // vilken webbsida som helst på internet kan skicka requests till min API. kan blir ett säkerhetsproblem. tillåter alla domäner att skicka requests till API:et, vilket är dålig praxis i produktion men inte direkt kopplat till något av mina säkerhetskrav. + origin: "*", // app.use inställt med stjärnan som origin innebär att alla domäner på internet kan skicka requests till min API. Det kan blir ett säkerhetsproblem, och är dålig praxis i produktion. // bör ändras till app.use(cors({origin: min-frontends-url})) })) app.use(express.json()) -app.use(mongoSanitize()) // här används mongoSanitize för att sanera input och skydda mot NoSQL-injection +app.use(mongoSanitize()) // här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. Säkerhetskrav 1. app.get("/", (req, res) => { res.send(listEndpoints(app)) @@ -160,7 +160,7 @@ app.post("/messages", authenticateUser, async (req, res) => { } }) -//backend: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs en giltig token för att skriva ett meddelande. Detta fanns redan med i koden från början. +//backend: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs en giltig token för att redigera ett meddelande. Detta fanns redan med i koden från början. app.patch("/messages/:id", authenticateUser, async (req, res) => { if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" }) try { @@ -195,7 +195,7 @@ app.delete("/messages/:id", authenticateUser, async (req, res) => { // här lägger jag till en kontroll för att säkerställa att endast ägaren av meddelandet kan radera det, annars kan vem som helst radera alla meddelanden. Säkerhetskrav 4. if (message.user.toString() !== req.user._id.toString()) { - console.log(`BLOCKED: User ${req.user._id} tried to delete message owned by ${message.user}`) + console.log(`BLOCKED: User ${req.user._id} tried to delete message owned by ${message.user}`) //console.log för att testa så att det fungerar return res.status(403).json({ error: "You can only delete your own messages" }) } diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index fc7f0b87..11fa4be8 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -92,10 +92,10 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => {isOwner && } {isOwner && !isEditing && ( - + // “edit”-knappen dyker bara upp för den som är inloggad och äger meddelandet. Detta var redan med från början. Säkerhetskrav 3 och 4. )} - {isOwner && isEditing && ( // “edit”-knappen dyker bara upp för den som är inloggad och äger meddelandet. säkerhetskrav 3 och 4. + {isOwner && isEditing && ( )} From 9212fde31e236b8e2b33919e55d9bc85e2007f6a Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Sun, 7 Jun 2026 23:02:44 +0200 Subject: [PATCH 19/40] =?UTF-8?q?sm=C3=A5fix=20av=20stavningar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/models/Message.js | 2 +- backend/server.js | 2 +- frontend/src/App.jsx | 27 ++++----------------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/backend/models/Message.js b/backend/models/Message.js index d2259636..d1c48e75 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -1,6 +1,6 @@ import mongoose from "mongoose" -// hängsle och livrem för att stoppa att databasen sparar inlägg som är för korta eller för långa +// Här stoppas att databasen sparar inlägg som är för korta eller för långa. Del av säkerhetskrav 10. const messageSchema = new mongoose.Schema({ message: { type: String, diff --git a/backend/server.js b/backend/server.js index af8ece1a..09594599 100644 --- a/backend/server.js +++ b/backend/server.js @@ -50,7 +50,7 @@ app.post("/register", async (req, res) => { message: `A user with this ${field} already exists` }) } - // här hashas lösenord med bcrypt innan det sparas i databasen, vilket åtgärdar säkerhetsåtgärd 5. + // här hashas lösenord med bcrypt innan det sparas i databasen, vilket åtgärdar säkerhetskrav 5. // bcrypt är en envägsfunktion, lösenordet kan inte återskapas från hashen. const hashedPassword = await bcrypt.hash(password, 10) const user = new User({ username: username.trim(), email, password: hashedPassword }) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4bc62565..a16cbde4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,16 +11,7 @@ export const App = () => { const [modal, setModal] = useState(null) const [error, setError] = useState(null) - // const fetchPosts = () => { - // setLoading(true) - // fetch(`${BASE_URL}/messages`) - // .then((res) => res.json()) - // .then((data) => setMessageList(data)) - // .catch((error) => console.error(error)) - // .finally(() => setLoading(false)) - // } - - //nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar nu med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. + //nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. const fetchPosts = () => { setLoading(true) fetch(`${BASE_URL}/messages`, { @@ -93,24 +84,14 @@ export const App = () => { )} {error &&

{error}

} - } - /> - + />} + //La till {user && så att Messagelist bara renderas om användaren är inloggad, så att ej autentiserade användare inte kan se meddelanden — del av säkerhetskrav 3 ) } From 06a308ecb19de322e65c7ad8e0eb77211feaf184 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Sun, 7 Jun 2026 23:03:11 +0200 Subject: [PATCH 20/40] =?UTF-8?q?till=C3=A4gg=20av=20ej=20fixade=20krav=20?= =?UTF-8?q?i=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6aeeb20..2d5b3155 100644 --- a/README.md +++ b/README.md @@ -1 +1,37 @@ -# yh-message-app-fullstack \ No newline at end of file +# yh-message-app-fullstack + +Säkerhetskrav som jag inte ändrat i koden: + +2. En användare ska inte kunna ändra sina behörigheter till admin + +Applikationen har ingen admin-roll implementerad, vilket innebär att det inte finns någon möjlighet för en användare att eskalera sina behörigheter till admin. User-modellen innehåller inget role-fält och det finns inga endpoints som tillåter en användare att ändra sina egna behörigheter. Alla användare har samma behörighetsnivå och kan endast påverka sina egna meddelanden. + +Att det inte finns några endpoints där användare kan ändra sina behörigheter betyder att det inte finns någon route i server.js, till exempel PATCH /users/:id, där en användare kan skicka in data för att uppdatera sitt eget konto — som till exempel { role: "admin" }. Eftersom den routen inte existerar finns det inget API-anrop att göra för att försöka ge sig själv högre behörigheter. + +Om vi i framtiden vill implementera en adminroll skulle det vara viktigt att: v +role aldrig sätts via API:et: en användare ska inte kunna skicka { role: "admin" } vid registrering eller via en PATCH-route och få det sparat +role ska sättas direkt i databasen: en administratör med databasåtkomst sätter rollen manuellt +requireAdmin-middleware: en kontroll som körs innan admin-routes och avvisar alla som inte har rätt roll +authenticateUser körs alltid före requireAdmin: annars kan man nå admin-routes utan att vara inloggad alls + +7. För att återställa lösenord krävs en engångskod skickad till användarens e-post. + +För att implementera lösenordsåterställning via engångskod behövs flera delar. Först behövs en e-posttjänst, till exempel Nodemailer, som kan skicka ett e-postmeddelande med en engångskod till användaren. + +Implementationen skulle börja med en POST /forgot-password-route som tar emot användarens e-postadress, genererar en tidsbegränsad engångskod och skickar den till användaren via e-post med exempelvis Nodemailer. + +I databasen behöver User-modellen utökas med ett fält för engångskoden och ett fält för när koden går ut, så att koden inte kan återanvändas eller användas efter att tidsgränsen passerat. + +Därefter behövs en POST /reset-password-route där användaren anger koden tillsammans med sitt nya lösenord. Backenden validerar att koden stämmer och inte har gått ut, varefter det nya lösenordet hashas med bcrypt och sparas i databasen, på samma sätt som vid registrering. + + +8. All kommunikation ska vara krypterad via HTTPS och TLS. +Att kommunikation ska vara krypterad via HTTPS och TLS fixas i Render när man deployar där. Render sätter automatiskt upp HTTPS och TLS för backenden. Det man kan göra är att se till att ens base_url i frontend pekar på en https://-adress och inte http:// + +Om man inte deployar via render så måste man skaffa ett TLS-certifikat, och en reverse proxy som tar emot https-anrop, dekrypterar och vidarebefordrar anropen till min express-server. + +9. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. + +För att logga vem som skapade, redigerade och raderade meddelanden bör en separat AuditLog-collection i databasen implementeras. Varje gång ett meddelande skapas, redigeras eller raderas sparas en logg-post med action (created, edited, deleted), meddelande-id, användar-id och tidsstämpel. + +Loggarna nås direkt i databasen via MongoDB Compass eller MongoDB Atlas av den med databasåtkomst. I en produktionssatt app med admin-roll skulle loggarna istället nås via en skyddad endpoint som endast administratörer har tillgång till. \ No newline at end of file From b2e1478cf10e901a53747b4daec6fe5df027027c Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Mon, 8 Jun 2026 15:58:49 +0200 Subject: [PATCH 21/40] =?UTF-8?q?sammanfattat=20gjorda=20=C3=A4ndringar=20?= =?UTF-8?q?i=20filen=20fas2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +------------ backend/middleware/rateLimiter.js | 2 +- backend/server.js | 29 +++++----- fas2.md | 64 +++++++++++++++++++++++ frontend/src/App.jsx | 6 +-- frontend/src/api.js | 5 +- frontend/src/components/PostMessage.jsx | 4 +- frontend/src/components/SingleMessage.jsx | 6 +-- 8 files changed, 91 insertions(+), 62 deletions(-) create mode 100644 fas2.md diff --git a/README.md b/README.md index 2d5b3155..3d6c1714 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,2 @@ # yh-message-app-fullstack - -Säkerhetskrav som jag inte ändrat i koden: - -2. En användare ska inte kunna ändra sina behörigheter till admin - -Applikationen har ingen admin-roll implementerad, vilket innebär att det inte finns någon möjlighet för en användare att eskalera sina behörigheter till admin. User-modellen innehåller inget role-fält och det finns inga endpoints som tillåter en användare att ändra sina egna behörigheter. Alla användare har samma behörighetsnivå och kan endast påverka sina egna meddelanden. - -Att det inte finns några endpoints där användare kan ändra sina behörigheter betyder att det inte finns någon route i server.js, till exempel PATCH /users/:id, där en användare kan skicka in data för att uppdatera sitt eget konto — som till exempel { role: "admin" }. Eftersom den routen inte existerar finns det inget API-anrop att göra för att försöka ge sig själv högre behörigheter. - -Om vi i framtiden vill implementera en adminroll skulle det vara viktigt att: v -role aldrig sätts via API:et: en användare ska inte kunna skicka { role: "admin" } vid registrering eller via en PATCH-route och få det sparat -role ska sättas direkt i databasen: en administratör med databasåtkomst sätter rollen manuellt -requireAdmin-middleware: en kontroll som körs innan admin-routes och avvisar alla som inte har rätt roll -authenticateUser körs alltid före requireAdmin: annars kan man nå admin-routes utan att vara inloggad alls - -7. För att återställa lösenord krävs en engångskod skickad till användarens e-post. - -För att implementera lösenordsåterställning via engångskod behövs flera delar. Först behövs en e-posttjänst, till exempel Nodemailer, som kan skicka ett e-postmeddelande med en engångskod till användaren. - -Implementationen skulle börja med en POST /forgot-password-route som tar emot användarens e-postadress, genererar en tidsbegränsad engångskod och skickar den till användaren via e-post med exempelvis Nodemailer. - -I databasen behöver User-modellen utökas med ett fält för engångskoden och ett fält för när koden går ut, så att koden inte kan återanvändas eller användas efter att tidsgränsen passerat. - -Därefter behövs en POST /reset-password-route där användaren anger koden tillsammans med sitt nya lösenord. Backenden validerar att koden stämmer och inte har gått ut, varefter det nya lösenordet hashas med bcrypt och sparas i databasen, på samma sätt som vid registrering. - - -8. All kommunikation ska vara krypterad via HTTPS och TLS. -Att kommunikation ska vara krypterad via HTTPS och TLS fixas i Render när man deployar där. Render sätter automatiskt upp HTTPS och TLS för backenden. Det man kan göra är att se till att ens base_url i frontend pekar på en https://-adress och inte http:// - -Om man inte deployar via render så måste man skaffa ett TLS-certifikat, och en reverse proxy som tar emot https-anrop, dekrypterar och vidarebefordrar anropen till min express-server. - -9. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. - -För att logga vem som skapade, redigerade och raderade meddelanden bör en separat AuditLog-collection i databasen implementeras. Varje gång ett meddelande skapas, redigeras eller raderas sparas en logg-post med action (created, edited, deleted), meddelande-id, användar-id och tidsstämpel. - -Loggarna nås direkt i databasen via MongoDB Compass eller MongoDB Atlas av den med databasåtkomst. I en produktionssatt app med admin-roll skulle loggarna istället nås via en skyddad endpoint som endast administratörer har tillgång till. \ No newline at end of file +Lagt till en fil med sammanfattning över de ändringar och förslag jag gjort för att uppfylla säkerhetskraven. Se filen fas2.md \ No newline at end of file diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 4ffacff4..6376d825 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -6,4 +6,4 @@ export const loginLimiter = rateLimit({ message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } }) -// här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Detta är en del av säkerhetskrav 6, som handlar om att implementera rate limiting för att skydda mot brute-force attacker. Sedan använder jag loginLimiter som middleware i app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file +// Säkerhetskrav 6. Här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 09594599..066e277c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,8 +11,8 @@ import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" import listEndpoints from "express-list-endpoints" import { loginLimiter } from "./middleware/rateLimiter.js" -// importerar middlewaren loginLimiter, vilket är en del av säkerhetskrav 6. -import mongoSanitize from "express-mongo-sanitize" //importerar för att sanera input och skydda mot NoSQL-injection, vilket är en del av säkerhetskrav 1. +// Säkerhetskrav 6: Importerar middlewaren loginLimiter +import mongoSanitize from "express-mongo-sanitize" //Säkerhetskrav 1: Importerar för att sanera input och skydda mot NoSQL-injection. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -25,7 +25,7 @@ app.use(cors({ })) app.use(express.json()) -app.use(mongoSanitize()) // här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. Säkerhetskrav 1. +app.use(mongoSanitize()) //Säkerhetskrav 1. Här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. app.get("/", (req, res) => { res.send(listEndpoints(app)) @@ -50,7 +50,7 @@ app.post("/register", async (req, res) => { message: `A user with this ${field} already exists` }) } - // här hashas lösenord med bcrypt innan det sparas i databasen, vilket åtgärdar säkerhetskrav 5. + // Säkerhetskrav 5. Här hashas lösenord med bcrypt innan det sparas i databasen. Detta fanns med från början i koden. // bcrypt är en envägsfunktion, lösenordet kan inte återskapas från hashen. const hashedPassword = await bcrypt.hash(password, 10) const user = new User({ username: username.trim(), email, password: hashedPassword }) @@ -80,7 +80,7 @@ app.post("/register", async (req, res) => { } }) -//lagt till middlewaren loginLimiter för att begränsa antalet inloggningsförsök, vilket är en del av säkerhetskrav 6. +//Säkerhetskrav 6: Lagt till middlewaren loginLimiter för att begränsa antalet inloggningsförsök. app.post("/login", loginLimiter, async (req, res) => { try { const { login, password } = req.body @@ -96,7 +96,7 @@ app.post("/login", loginLimiter, async (req, res) => { }) } - // här jämförs det angivna lösenordet som hashas med det hashade lösenordet i databasen, vilket är en del av säkerhetsåtgärd 5. + // Säkerhetskrav 5: Här jämförs det angivna lösenordet som hashas med det hashade lösenordet i databasen. Detta fanns från början och jag har inte ändrat något. const passwordMatch = await bcrypt.compare(password, user.password) if (!passwordMatch) { return res.status(401).json({ @@ -132,7 +132,7 @@ app.post("/login", loginLimiter, async (req, res) => { const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) -//lagt till authenticateUser i app.get("/messages") så att endast inloggade användare kan se meddelanden. Det är en del av mitt säkerhetskrav 3, alltså att endast inloggade användare ska kunna se meddelanden. +//Säkerhetskrav 3. Lagt till middlewaren authenticateUser så att endast inloggade användare kan se meddelanden. app.get("/messages", authenticateUser, async (req, res) => { try { const messages = await Message.find() @@ -145,11 +145,11 @@ app.get("/messages", authenticateUser, async (req, res) => { res.status(500).json({ message: "Could not fetch messages" }) } }) -//att authenicateUser finns med i app.post, gör att det krävs en giltig token för att skriva ett meddelande. Detta fanns redan med i koden från början. Uppfyller säkerhetskrav 3. +//Säkerhetskrav 3. authenticateUser finns med i app.post, vilket gör att inloggning krävs för att posta ett meddelande. Detta fanns redan med i koden från början. app.post("/messages", authenticateUser, async (req, res) => { const text = req.body.message if (!text || text.length < 3 || text.length > 140) { - return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // detta är en del av säkerhetskrav 10, begränsing av längd på meddelanden. + return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // Säkerhetskrav 10. Ändrat begränsning av längd på meddelanden. } const message = new Message({ message: req.body.message, user: req.user._id }) try { @@ -160,18 +160,18 @@ app.post("/messages", authenticateUser, async (req, res) => { } }) -//backend: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs en giltig token för att redigera ett meddelande. Detta fanns redan med i koden från början. +//Säkerhetskrav 3: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs att man är inloggad för att redigera ett meddelande. Detta fanns med i koden från början. app.patch("/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" }) - //här under säkerställs att endast ägaren av meddelandet kan redigera det, annars kan vem som helst redigera alla meddelanden. detta var med i koden från början och uppfyller säkerhetskrav 4. + //Säkerhetskrav 4. Här under säkerställs att endast ägaren av meddelandet kan redigera det, genom att man jämför den inloggades id med id på meddelandet. Annars kan vem som helst redigera alla meddelanden. Detta var med i koden från början. if (message.user.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "You can only edit your own messages" }) } - // här lägger jag till en kontroll för att säkerställa att meddelandet är mellan 3 och 140 tecken, annars kan användare skicka in väldigt långa eller väldigt korta meddelanden. Detta är en del av säkerhetskrav 10. + //Säkerhetskrav 10: Här lägger jag till en kontroll för att säkerställa att meddelandet som redigeras är mellan 3 och 140 tecken. const editedText = req.body.editedMessage if (!editedText || editedText.length < 3 || editedText.length > 140) { return res.status(400).json({ error: "Message must be between 3 and 140 characters" }) @@ -186,16 +186,15 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { } }) -// La till authenticateUser. Det löser delvis krav 4, alltså att ägarskap ska kontrolleras innan radering av meddelanden. +// Säkerhetskrav 3/4: La till authenticateUser vid radering av meddelande, vilket gör att man måste vara inloggad för att ändra meddelande. 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" }) - // här lägger jag till en kontroll för att säkerställa att endast ägaren av meddelandet kan radera det, annars kan vem som helst radera alla meddelanden. Säkerhetskrav 4. + // Säkerhetskrav 4. Här lägger jag till samma kontroll som finns vid redigering av meddelande för att säkerställa att endast ägaren av meddelandet kan radera det. Jämförelse av den inloggades id och meddelandets id. if (message.user.toString() !== req.user._id.toString()) { - console.log(`BLOCKED: User ${req.user._id} tried to delete message owned by ${message.user}`) //console.log för att testa så att det fungerar return res.status(403).json({ error: "You can only delete your own messages" }) } diff --git a/fas2.md b/fas2.md new file mode 100644 index 00000000..7f1917d4 --- /dev/null +++ b/fas2.md @@ -0,0 +1,64 @@ +Kodändringar och förslag för att uppfylla säkerhetskraven. + +1. Krav: Backend ska validera och sanitera input, samt säkert hantera input från användare så att det inte kan tolkas som databaskommandon. + +Fixat genom att importera och använda mongoSanitize i server.js. Se filen för ändringarna. + +2. Krav: En användare ska inte kunna ändra sina behörigheter till admin + +Applikationen har ingen admin-roll implementerad, vilket innebär att det idag inte finns någon möjlighet för en användare att eskalera sina behörigheter till admin. User-modellen innehåller inget role-fält och det finns inga endpoints som tillåter en användare att ändra sina egna behörigheter. Alla användare har samma behörighetsnivå och kan endast påverka sina egna meddelanden. + +Att det inte finns några endpoints där användare kan ändra sina behörigheter betyder att det inte finns någon route i server.js, till exempel PATCH /users/:id, där en användare kan skicka in data för att uppdatera sitt eget konto — som till exempel { role: "admin" }. Eftersom den routen inte existerar finns det inget API-anrop att göra för att försöka ge sig själv högre behörigheter. + +Om vi i framtiden vill implementera en adminroll skulle det vara viktigt att: + +role aldrig sätts via API:et: en användare ska inte kunna skicka { role: "admin" } vid registrering eller via en PATCH-route och få det sparat + +role ska sättas direkt i databasen: en administratör med databasåtkomst sätter rollen manuellt + +requireAdmin-middleware: en kontroll som körs innan admin-routes och avvisar alla som inte har rätt roll + +authenticateUser körs alltid före requireAdmin: annars kan man nå admin-routes utan att vara inloggad alls + +3. Krav: Systemet ska kräva inloggning för att en användare ska kunna se, skriva eller redigera meddelanden. + +Uppfylldes till viss del redan, men har också gjort ändringar i backend och frontend för att uppfylla kravet. Se ändringar gjorda i server.js, app.jsx, samt postmessage.jsx och singlemessage.jsx. + +4. Krav: Innan redigering eller radering av ett meddelande ska systemet kontrollera att meddelandet är kopplat till den inloggade användaren. + +Uppfylldes till viss del redan vad gällde redigering av meddelande, men jag har gjort förändringar för radering av meddelanden. Se ändringar i server.js, singlemessage.jsx + +5. Krav: Lösenord ska aldrig lagras i klartext i databasen. + +Detta krav uppfylldes redan i koden. Se server.js för kommentarer. + +6. Krav: Systemet ska blockera inloggning efter 5 misslyckade försök. + +Detta la jag till genom rate-limiting. Se ny middleware i rateLimiter.js och användning av den i server.js + +7. Krav: För att återställa lösenord krävs en engångskod skickad till användarens e-post. + +För att implementera lösenordsåterställning via engångskod behövs flera delar. För det första behövs e funktion för att återställa lösenordet, samt en e-posttjänst, till exempel Nodemailer, som kan skicka ett e-postmeddelande med en engångskod till användaren. + +Implementationen skulle börja med en POST /forgot-password-route som tar emot användarens e-postadress, genererar en tidsbegränsad engångskod och skickar den till användaren via e-post med exempelvis Nodemailer. + +I databasen behöver User-modellen utökas med ett fält för engångskoden och ett fält för när koden går ut, så att koden inte kan återanvändas eller användas efter att tidsgränsen passerat. + +Därefter behövs en POST /reset-password-route där användaren anger koden tillsammans med sitt nya lösenord. Backenden validerar att koden stämmer och inte har gått ut, varefter det nya lösenordet hashas med bcrypt och sparas i databasen, på samma sätt som vid registrering. + + +8. Krav: All kommunikation ska vara krypterad via HTTPS och TLS. + +För att anropen ska ske via HTTPS är det viktigt att BASE_URL i api.js pekar mot en https-adress. Just nu är URL:n i api.js en lokal host för utveckling, som går över http, men i produktion måste det vara en HTTPS-adress för att uppfylla säkerhetskravet- + +För att kommunikationen ska vara krypterad enligt TLS behöver vi skaffa ett TLS-certifikat, och en reverse-proxy som tar emot https-anrop, dekrypterar och vidarebefordrar anropen till min vår express-server. + +9. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. + +För att logga vem som skapade, redigerade och raderade meddelanden bör en separat AuditLog-collection i databasen implementeras. Varje gång ett meddelande skapas, redigeras eller raderas sparas en logg-post med action (created, edited, deleted), meddelande-id, användar-id och tidsstämpel. + +Loggarna nås direkt i databasen via MongoDB Compass eller MongoDB Atlas av den med databasåtkomst. I en produktionssatt app med admin-roll skulle loggarna istället nås via en skyddad endpoint som endast administratörer har tillgång till. + +10. Krav: Ett meddelande ska vara minst 3 och max 140 tecken. + +Implementerat. Se ändringar i server.js, message.js, samt singlemessage.jsx och postmessage.jsx \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a16cbde4..011e67c7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,7 +11,7 @@ export const App = () => { const [modal, setModal] = useState(null) const [error, setError] = useState(null) - //nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. + //Säkerhetskrav 3: Nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. const fetchPosts = () => { setLoading(true) fetch(`${BASE_URL}/messages`, { @@ -29,7 +29,7 @@ export const App = () => { if (user) fetchPosts() }, [user]) - // ovan har gjorts om för att endast inloggade användare ska kunna se meddelanden. Nedan är den tidigare versionen där fetchPosts kördes varje gång appen laddades, oavsett om en användare var inloggad eller inte. Det är en del av mitt säkerhetskrav 4, alltså att endast autentiserade användare ska kunna se meddelanden. + // Säkerhetskrav 3. Ovan har gjorts om för att endast inloggade användare ska kunna se meddelanden. Nedan är den tidigare versionen där fetchPosts kördes varje gång appen laddades, oavsett om en användare var inloggad eller inte. //useEffect(() => { // fetchPosts() // }, []) @@ -92,6 +92,6 @@ export const App = () => { user={user} onUnauthorized={handleUnauthorized} />} - //La till {user && så att Messagelist bara renderas om användaren är inloggad, så att ej autentiserade användare inte kan se meddelanden — del av säkerhetskrav 3 + //Säkerhetskrav 3: La till {user && så att Messagelist bara renderas om användaren är inloggad, så att icke-autentiserade användare inte kan se meddelanden. ) } diff --git a/frontend/src/api.js b/frontend/src/api.js index 73caeaf6..87ce50fe 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1,4 +1,5 @@ export const BASE_URL = "http://localhost:3000" -//export const BASE_URL = "https://yh-message-app-fullstack.onrender.com" -//den gamla! \ No newline at end of file +//BASE_URL är ändrad till localhost för utveckling. när projektet ska deployas är det viktigt att url:n är en https://-adress för att uppfylla säkerhetskrav 8. + +// den tidigare adressen: export const BASE_URL = "https://yh-message-app-fullstack.onrender.com" diff --git a/frontend/src/components/PostMessage.jsx b/frontend/src/components/PostMessage.jsx index 4345e5db..310a1877 100644 --- a/frontend/src/components/PostMessage.jsx +++ b/frontend/src/components/PostMessage.jsx @@ -10,7 +10,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => event.preventDefault() setSubmitting(true) - // detta sätts in för att validera i frontenden att meddelandet som postas är mellan 3 och 140 tecken, säkerhetskrav 10. + // Säkerhetskrav 10. Detta sätts in för att validera i frontenden att meddelandet som postas är mellan 3 och 140 tecken. if (newPost.length < 3 || newPost.length > 140) { setErrorMessage("Message must be between 3 and 140 characters") setSubmitting(false) @@ -58,7 +58,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => if (!user) { return

Log in to see and write messages

- } // Om man inte är inloggad renderas inte formuläret för att skriva meddelanden. Meddelandet ändrat för att förtydliga att användaren måste logga in för att både se och skriva meddelanden. säkerhetskrav 3. + } //Säkerhetskrav 3. Denna kodrad gör att formuläret för att skriva ett meddelande inte renderas om man inte är inloggad. Meddelandet som visas har jag ändrat för att förtydliga att användaren måste logga in för att både se och skriva meddelanden. return (
diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index 11fa4be8..cb4614b4 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -29,7 +29,7 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => } const onSave = async () => { - //lägger här in validering i frontenden att meddelande som redigeras måste vara mellan 3 och 140 tecken. säkerhetskrav 10. + //säkerhetskrav 10. Lägger här in validering i frontenden att meddelande som redigeras måste vara mellan 3 och 140 tecken. if (editedText.length < 3 || editedText.length > 140) { setEditError("Message must be between 3 and 140 characters") return @@ -87,12 +87,12 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) =>
)} - {/* för att endast ägaren av meddelandet ska kunna radera har isOwner lagts till som villkor på deleteknappen. Då visas den inte för andra användare, vilket är en del av mitt säkerhetskrav 4. */} + {/* Säkerhetskrav 4. För att endast ägaren av meddelandet ska kunna radera har isOwner lagts till som villkor på deleteknappen. Det gör att delete-knappen bara visas för användare som äger meddelandet. Kosmetisk del, det riktiga skyddet sitter i backend. */}
{isOwner && } {isOwner && !isEditing && ( - // “edit”-knappen dyker bara upp för den som är inloggad och äger meddelandet. Detta var redan med från början. Säkerhetskrav 3 och 4. + // Säkerhetskrav 4. “Edit”-knappen dyker bara upp för den som är inloggad och äger meddelandet (geno isOWner) Detta var med i koden redan från början. )} {isOwner && isEditing && ( From 9bfae0dab6f8a6d7a77af7983722fc208448a161 Mon Sep 17 00:00:00 2001 From: karinisaksson <152193321+karinisaksson@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:02:06 +0200 Subject: [PATCH 22/40] fixade stavfel --- fas2.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fas2.md b/fas2.md index 7f1917d4..0f627eac 100644 --- a/fas2.md +++ b/fas2.md @@ -38,7 +38,7 @@ Detta la jag till genom rate-limiting. Se ny middleware i rateLimiter.js och anv 7. Krav: För att återställa lösenord krävs en engångskod skickad till användarens e-post. -För att implementera lösenordsåterställning via engångskod behövs flera delar. För det första behövs e funktion för att återställa lösenordet, samt en e-posttjänst, till exempel Nodemailer, som kan skicka ett e-postmeddelande med en engångskod till användaren. +För att implementera lösenordsåterställning via engångskod behövs flera delar. För det första behövs en funktion för att återställa lösenordet, samt en e-posttjänst, till exempel Nodemailer, som kan skicka ett e-postmeddelande med en engångskod till användaren. Implementationen skulle börja med en POST /forgot-password-route som tar emot användarens e-postadress, genererar en tidsbegränsad engångskod och skickar den till användaren via e-post med exempelvis Nodemailer. @@ -49,9 +49,9 @@ Därefter behövs en POST /reset-password-route där användaren anger koden til 8. Krav: All kommunikation ska vara krypterad via HTTPS och TLS. -För att anropen ska ske via HTTPS är det viktigt att BASE_URL i api.js pekar mot en https-adress. Just nu är URL:n i api.js en lokal host för utveckling, som går över http, men i produktion måste det vara en HTTPS-adress för att uppfylla säkerhetskravet- +För att anropen ska ske via HTTPS är det viktigt att BASE_URL i api.js pekar mot en https-adress. Just nu är URL:n i api.js en lokal host för utveckling, som går över http, men i produktion måste det vara en HTTPS-adress för att uppfylla säkerhetskravet. -För att kommunikationen ska vara krypterad enligt TLS behöver vi skaffa ett TLS-certifikat, och en reverse-proxy som tar emot https-anrop, dekrypterar och vidarebefordrar anropen till min vår express-server. +För att kommunikationen ska vara krypterad enligt TLS behöver vi skaffa ett TLS-certifikat. Det fixas inte i koden utan när vi deployar. 9. Systemet ska logga vem som skapade, redigerade och raderade varje meddelande. @@ -61,4 +61,4 @@ Loggarna nås direkt i databasen via MongoDB Compass eller MongoDB Atlas av den 10. Krav: Ett meddelande ska vara minst 3 och max 140 tecken. -Implementerat. Se ändringar i server.js, message.js, samt singlemessage.jsx och postmessage.jsx \ No newline at end of file +Implementerat. Se ändringar i server.js, message.js, samt singlemessage.jsx och postmessage.jsx From 63fe4e63ace4f3c37f5ee1379f50d45d792c5d4a Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Mon, 8 Jun 2026 16:04:17 +0200 Subject: [PATCH 23/40] =?UTF-8?q?f=C3=B6rtydligande?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/server.js b/backend/server.js index 066e277c..c903fa2c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -20,7 +20,7 @@ const PORT = process.env.PORT || "3000" const app = express() app.use(helmet()) app.use(cors({ - origin: "*", // app.use inställt med stjärnan som origin innebär att alla domäner på internet kan skicka requests till min API. Det kan blir ett säkerhetsproblem, och är dålig praxis i produktion. + origin: "*", //Extra: app.use(cors inställt med * som origin innebär att alla domäner på internet kan skicka requests till min API. Det kan bli ett säkerhetsproblem, och är dålig praxis i produktion. // bör ändras till app.use(cors({origin: min-frontends-url})) })) From d0d463e0e2be216484c3062cc5ed4ecfae4583d3 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Mon, 8 Jun 2026 16:11:07 +0200 Subject: [PATCH 24/40] =?UTF-8?q?f=C3=B6rtyligande=20krav=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/server.js b/backend/server.js index c903fa2c..31ff5f72 100644 --- a/backend/server.js +++ b/backend/server.js @@ -25,7 +25,7 @@ app.use(cors({ })) app.use(express.json()) -app.use(mongoSanitize()) //Säkerhetskrav 1. Här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. +app.use(mongoSanitize()) //Säkerhetskrav 1. Här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. Vilket innebär att all inkommande request-data (body, params, query) automatiskt rensas från MongoDB-operatorer som $ och . innan de når någon route. app.get("/", (req, res) => { res.send(listEndpoints(app)) From 514593eec0bce9b6a5a00c7a6c6425a570773320 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 9 Jun 2026 12:57:25 +0200 Subject: [PATCH 25/40] =?UTF-8?q?fas=203:=20=C3=A5tg=C3=A4rdade=20s=C3=A5r?= =?UTF-8?q?barheter=20med=20ratelimiter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/rateLimiter.js | 10 +++++++++- backend/server.js | 20 +++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 6376d825..86886a60 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -6,4 +6,12 @@ export const loginLimiter = rateLimit({ message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } }) -// Säkerhetskrav 6. Här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. \ No newline at end of file +// Säkerhetskrav 6. Här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. + + +// Efter att ha använt CodeQL och hittat sårbarheter att jag en till limiter för att använda på övriga endpoints som gör databasanrop utan någon begränsning. Annars kan en angripare skicka tusentals requests per sekund och överlasta databas/server. +export const generalLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minuter + max: 100, // 100 requests per 15 min per IP + message: { success: false, message: "Too many requests, please try again later" } +}) \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 31ff5f72..e0df2849 100644 --- a/backend/server.js +++ b/backend/server.js @@ -10,8 +10,9 @@ import { User } from "./models/User.js" import { authenticateUser } from "./middleware/auth.js" import "./config/db.js" import listEndpoints from "express-list-endpoints" -import { loginLimiter } from "./middleware/rateLimiter.js" -// Säkerhetskrav 6: Importerar middlewaren loginLimiter +import { loginLimiter, generalLimiter } from "./middleware/rateLimiter.js" +// Säkerhetskrav 6: Importerar middlewaren loginLimiter +//fas 3: importerar även nya ratelimitern generalLimiter import mongoSanitize from "express-mongo-sanitize" //Säkerhetskrav 1: Importerar för att sanera input och skydda mot NoSQL-injection. if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -31,7 +32,8 @@ app.get("/", (req, res) => { res.send(listEndpoints(app)) }) -app.post("/register", async (req, res) => { +//applicerar generalLimiter +app.post("/register", generalLimiter, async (req, res) => { try { const { email, password, username } = req.body @@ -133,7 +135,8 @@ app.post("/login", loginLimiter, async (req, res) => { const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) //Säkerhetskrav 3. Lagt till middlewaren authenticateUser så att endast inloggade användare kan se meddelanden. -app.get("/messages", authenticateUser, async (req, res) => { +// fas 3: lägger till generalLimiter +app.get("/messages", authenticateUser, generalLimiter, async (req, res) => { try { const messages = await Message.find() .sort({ createdAt: "desc" }) @@ -146,7 +149,8 @@ app.get("/messages", authenticateUser, async (req, res) => { } }) //Säkerhetskrav 3. authenticateUser finns med i app.post, vilket gör att inloggning krävs för att posta ett meddelande. Detta fanns redan med i koden från början. -app.post("/messages", authenticateUser, async (req, res) => { +//fas 3: lägger till generalLimiter +app.post("/messages", authenticateUser, generalLimiter, async (req, res) => { const text = req.body.message if (!text || text.length < 3 || text.length > 140) { return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // Säkerhetskrav 10. Ändrat begränsning av längd på meddelanden. @@ -161,7 +165,8 @@ app.post("/messages", authenticateUser, async (req, res) => { }) //Säkerhetskrav 3: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs att man är inloggad för att redigera ett meddelande. Detta fanns med i koden från början. -app.patch("/messages/:id", authenticateUser, async (req, res) => { +//fas 3: lägger till generalLimiter +app.patch("/messages/:id", authenticateUser, generalLimiter, 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) @@ -187,7 +192,8 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { }) // Säkerhetskrav 3/4: La till authenticateUser vid radering av meddelande, vilket gör att man måste vara inloggad för att ändra meddelande. -app.delete("/messages/:id", authenticateUser, async (req, res) => { +//fas 3: lägger till generalLimiter +app.delete("/messages/:id", authenticateUser, generalLimiter, 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) From 104e2f087f4ac0c82a1d100d3889fbf92402ce93 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 9 Jun 2026 13:02:45 +0200 Subject: [PATCH 26/40] =?UTF-8?q?fas=203:=20f=C3=B6rtdligade=20generalLimi?= =?UTF-8?q?ter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/rateLimiter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 86886a60..3b786bd4 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -6,10 +6,10 @@ export const loginLimiter = rateLimit({ message: { success: false, message: "Too many login attempts, please try again after 15 minutes" } }) -// Säkerhetskrav 6. Här har jag lagt in en funktion LoginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. +// Säkerhetskrav 6. Här har jag lagt in en funktion loginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. -// Efter att ha använt CodeQL och hittat sårbarheter att jag en till limiter för att använda på övriga endpoints som gör databasanrop utan någon begränsning. Annars kan en angripare skicka tusentals requests per sekund och överlasta databas/server. +// Efter att ha använt CodeQL och hittat sårbarheter la jag till ytterligare en limiter för att använda på övriga endpoints som gör databasanrop. Annars kan en angripare skicka tusentals requests per sekund och överlasta databas/server. Satte ett högre tak än loginLimiter eftersom 5 anrop på 15 minuter skulle göra appen oanvändbar på tex /messages GET/POST/PATCH/DELETE. export const generalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minuter max: 100, // 100 requests per 15 min per IP From dedd1e7fe114c876c9ba4d598faa4e3b8187487f Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 9 Jun 2026 13:09:59 +0200 Subject: [PATCH 27/40] =?UTF-8?q?fas=203:=20generalLimiter=20i=20r=C3=A4tt?= =?UTF-8?q?=20ordning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/server.js b/backend/server.js index e0df2849..4ca68bd2 100644 --- a/backend/server.js +++ b/backend/server.js @@ -136,7 +136,7 @@ const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) //Säkerhetskrav 3. Lagt till middlewaren authenticateUser så att endast inloggade användare kan se meddelanden. // fas 3: lägger till generalLimiter -app.get("/messages", authenticateUser, generalLimiter, async (req, res) => { +app.get("/messages", generalLimiter, authenticateUser, async (req, res) => { try { const messages = await Message.find() .sort({ createdAt: "desc" }) @@ -150,7 +150,7 @@ app.get("/messages", authenticateUser, generalLimiter, async (req, res) => { }) //Säkerhetskrav 3. authenticateUser finns med i app.post, vilket gör att inloggning krävs för att posta ett meddelande. Detta fanns redan med i koden från början. //fas 3: lägger till generalLimiter -app.post("/messages", authenticateUser, generalLimiter, async (req, res) => { +app.post("/messages", generalLimiter, authenticateUser, async (req, res) => { const text = req.body.message if (!text || text.length < 3 || text.length > 140) { return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // Säkerhetskrav 10. Ändrat begränsning av längd på meddelanden. @@ -166,7 +166,7 @@ app.post("/messages", authenticateUser, generalLimiter, async (req, res) => { //Säkerhetskrav 3: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs att man är inloggad för att redigera ett meddelande. Detta fanns med i koden från början. //fas 3: lägger till generalLimiter -app.patch("/messages/:id", authenticateUser, generalLimiter, async (req, res) => { +app.patch("/messages/:id", generalLimiter, 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) @@ -193,7 +193,7 @@ app.patch("/messages/:id", authenticateUser, generalLimiter, async (req, res) => // Säkerhetskrav 3/4: La till authenticateUser vid radering av meddelande, vilket gör att man måste vara inloggad för att ändra meddelande. //fas 3: lägger till generalLimiter -app.delete("/messages/:id", authenticateUser, generalLimiter, async (req, res) => { +app.delete("/messages/:id", generalLimiter, 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) From 85337554ba2d77f95f0ad0a633c2ad87b9685a9a Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 9 Jun 2026 13:20:15 +0200 Subject: [PATCH 28/40] fas 3: CORS-config --- backend/server.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/server.js b/backend/server.js index 4ca68bd2..fcef780e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -20,11 +20,12 @@ 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()) +// app.use(cors({ +// origin: "*" app.use(cors({ - origin: "*", //Extra: app.use(cors inställt med * som origin innebär att alla domäner på internet kan skicka requests till min API. Det kan bli ett säkerhetsproblem, och är dålig praxis i produktion. - // bör ändras till app.use(cors({origin: min-frontends-url})) + origin: "http://localhost:5173", +})) // fas 3: ändrat så att bara frontend får skicka requests till API/backend. När det var * som origin kunde vilken domän som helst skicka requests till min backend. Hittade detta själv men åtgärdade i fas 3 efter att även CodeQL påpekat det. -})) app.use(express.json()) app.use(mongoSanitize()) //Säkerhetskrav 1. Här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. Vilket innebär att all inkommande request-data (body, params, query) automatiskt rensas från MongoDB-operatorer som $ och . innan de når någon route. From d10ec93e3580eb3d9de3a54fe2a1b533299c6d84 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Tue, 9 Jun 2026 14:18:48 +0200 Subject: [PATCH 29/40] =?UTF-8?q?fas=203:=20generallimiter=20p=C3=A5=20all?= =?UTF-8?q?a=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/server.js b/backend/server.js index fcef780e..e9bd4d34 100644 --- a/backend/server.js +++ b/backend/server.js @@ -25,16 +25,15 @@ app.use(helmet()) app.use(cors({ origin: "http://localhost:5173", })) // fas 3: ändrat så att bara frontend får skicka requests till API/backend. När det var * som origin kunde vilken domän som helst skicka requests till min backend. Hittade detta själv men åtgärdade i fas 3 efter att även CodeQL påpekat det. - app.use(express.json()) app.use(mongoSanitize()) //Säkerhetskrav 1. Här används mongoSanitize för att sanera input och skydda mot NoSQL-injection. Vilket innebär att all inkommande request-data (body, params, query) automatiskt rensas från MongoDB-operatorer som $ och . innan de når någon route. +app.use(generalLimiter) // fas 3: använder generalLimiter globalt på alla endpoints, vilket gör att alla nya rutter skyddas automatiskt istället för att lägga till limitern manuellt vid varje ny endpoint. app.get("/", (req, res) => { res.send(listEndpoints(app)) }) -//applicerar generalLimiter -app.post("/register", generalLimiter, async (req, res) => { +app.post("/register", async (req, res) => { try { const { email, password, username } = req.body @@ -136,8 +135,7 @@ app.post("/login", loginLimiter, async (req, res) => { const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) //Säkerhetskrav 3. Lagt till middlewaren authenticateUser så att endast inloggade användare kan se meddelanden. -// fas 3: lägger till generalLimiter -app.get("/messages", generalLimiter, authenticateUser, async (req, res) => { +app.get("/messages", authenticateUser, async (req, res) => { try { const messages = await Message.find() .sort({ createdAt: "desc" }) @@ -150,8 +148,7 @@ app.get("/messages", generalLimiter, authenticateUser, async (req, res) => { } }) //Säkerhetskrav 3. authenticateUser finns med i app.post, vilket gör att inloggning krävs för att posta ett meddelande. Detta fanns redan med i koden från början. -//fas 3: lägger till generalLimiter -app.post("/messages", generalLimiter, authenticateUser, async (req, res) => { +app.post("/messages", authenticateUser, async (req, res) => { const text = req.body.message if (!text || text.length < 3 || text.length > 140) { return res.status(400).json({ message: "Message must be between 3 and 140 characters" }) // Säkerhetskrav 10. Ändrat begränsning av längd på meddelanden. @@ -166,8 +163,7 @@ app.post("/messages", generalLimiter, authenticateUser, async (req, res) => { }) //Säkerhetskrav 3: Att authenicateUser finns med i app.patch(“/messages/:id gör att det krävs att man är inloggad för att redigera ett meddelande. Detta fanns med i koden från början. -//fas 3: lägger till generalLimiter -app.patch("/messages/:id", generalLimiter, authenticateUser, async (req, res) => { +app.patch("/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) @@ -193,8 +189,7 @@ app.patch("/messages/:id", generalLimiter, authenticateUser, async (req, res) => }) // Säkerhetskrav 3/4: La till authenticateUser vid radering av meddelande, vilket gör att man måste vara inloggad för att ändra meddelande. -//fas 3: lägger till generalLimiter -app.delete("/messages/:id", generalLimiter, authenticateUser, async (req, res) => { +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) From b04b5085a16fea1022ca835b8e912a11f20c0466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:57:31 +0000 Subject: [PATCH 30/40] Bump jsonwebtoken from 8.5.1 to 9.0.0 in /backend Bumps [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) from 8.5.1 to 9.0.0. - [Changelog](https://github.com/auth0/node-jsonwebtoken/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jsonwebtoken/compare/v8.5.1...v9.0.0) --- updated-dependencies: - dependency-name: jsonwebtoken dependency-version: 9.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/package-lock.json | 113 +++++++------------------------------- backend/package.json | 2 +- 2 files changed, 20 insertions(+), 95 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 90e93b82..f5c0dd44 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -16,7 +16,7 @@ "express-mongo-sanitize": "^2.2.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" } @@ -41,18 +41,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", @@ -1089,25 +1077,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": { @@ -1146,46 +1128,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": { @@ -1590,18 +1536,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", @@ -1853,12 +1787,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.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz", + "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==", "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -2014,18 +1951,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", diff --git a/backend/package.json b/backend/package.json index 1c17c054..6800df1b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -17,7 +17,7 @@ "express-mongo-sanitize": "^2.2.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" } From a6e2d1ab0c0653d3a9732fcde3a131053fdbd0b5 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 10 Jun 2026 00:51:58 +0200 Subject: [PATCH 31/40] =?UTF-8?q?fas=203:=20uppdaterade=20bcrypt=20till=20?= =?UTF-8?q?v6=20to=20f=C3=B6r=20att=20ta=20bort=20tar-dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package-lock.json | 607 ++------------------------------------ backend/package.json | 2 +- 2 files changed, 20 insertions(+), 589 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index f5c0dd44..35834289 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,7 +8,7 @@ "name": "yh-message-app-fullstack", "version": "1.0.0", "dependencies": { - "bcrypt": "^5.1.1", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -21,26 +21,6 @@ "nodemon": "^3.1.3" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, "node_modules/@mongodb-js/saslprep": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz", @@ -65,12 +45,6 @@ "@types/webidl-conversions": "*" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -84,50 +58,6 @@ "node": ">= 0.6" } }, - "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/agent-base/node_modules/debug": { - "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" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -141,26 +71,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -177,17 +87,17 @@ } }, "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 18" } }, "node_modules/binary-extensions": { @@ -327,36 +237,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -419,12 +299,6 @@ "ms": "2.0.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT" - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -444,15 +318,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "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==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -494,12 +359,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -684,36 +543,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -737,27 +566,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -795,27 +603,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "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": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -828,34 +615,6 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -889,12 +648,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC" - }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -936,42 +689,6 @@ "url": "https://opencollective.com/express" } }, - "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": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "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" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -990,17 +707,6 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "license": "ISC" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1046,15 +752,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1134,30 +831,6 @@ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1248,52 +921,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mongodb": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", @@ -1438,51 +1065,23 @@ } }, "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", + "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": "^18 || ^20 || >= 21" } }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, "node_modules/nodemon": { @@ -1536,21 +1135,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1560,19 +1144,6 @@ "node": ">=0.10.0" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1606,15 +1177,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1624,15 +1186,6 @@ "node": ">= 0.8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -1718,20 +1271,6 @@ "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1744,22 +1283,6 @@ "node": ">=8.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1843,12 +1366,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1933,12 +1450,6 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1969,41 +1480,6 @@ "node": ">= 0.8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2016,24 +1492,6 @@ "node": ">=4" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "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", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2104,12 +1562,6 @@ "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -2149,27 +1601,6 @@ "engines": { "node": ">=18" } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" } } } diff --git a/backend/package.json b/backend/package.json index 6800df1b..bd01d79d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,7 @@ "dev": "nodemon server.js" }, "dependencies": { - "bcrypt": "^5.1.1", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", From 24a87af060f8aa69eff0ed6fd788a0284d6b9db1 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 10 Jun 2026 11:06:08 +0200 Subject: [PATCH 32/40] =?UTF-8?q?Uppgraderade=20Vite=20f=C3=B6r=20att=20fi?= =?UTF-8?q?xa=20dependabot=20security=20alarms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 1730 +++++++++++++++--------------------- frontend/package.json | 6 +- 2 files changed, 743 insertions(+), 993 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e43d824e..9f2ec9cf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,326 +15,229 @@ "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", + "@vitejs/plugin-react": "^6.0.2", "eslint": "^8.45.0", "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": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", - "dev": true, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "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, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "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, "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "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, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=6.9.0" + "node": ">=10.10.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "BSD-3-Clause" }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "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": { - "@babel/helper-plugin-utils": "^7.27.1" + "@tybys/wasm-util": "^0.10.2" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=6.9.0" + "node": ">= 8" } }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">= 8" } }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "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": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=6.9.0" + "node": ">= 8" } }, - "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" - ], + "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", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "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==", + "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" ], @@ -345,30 +248,13 @@ "android" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "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" ], @@ -379,13 +265,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "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" ], @@ -396,30 +282,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "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==", + "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" ], @@ -430,503 +299,232 @@ "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "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": "BSD-3-Clause" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "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, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "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, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "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, + "libc": [ + "glibc" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "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, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "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, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "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": ">= 8" + "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-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": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "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", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "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": { - "@babel/types": "^7.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "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", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } + "license": "MIT" }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "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": { - "@babel/types": "^7.28.2" + "tslib": "^2.4.0" } }, "node_modules/@types/prop-types": { @@ -965,24 +563,29 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/acorn": { @@ -1229,19 +832,6 @@ "dev": true, "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", - "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", @@ -1253,40 +843,6 @@ "concat-map": "0.0.1" } }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -1347,27 +903,6 @@ "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001793", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", - "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1412,13 +947,6 @@ "dev": true, "license": "MIT" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1572,6 +1100,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", @@ -1600,13 +1138,6 @@ "node": ">= 0.4" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.359", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.359.tgz", - "integrity": "sha512-8lPELWuYZIWk7NDvCNthtmMw/7Q5Wu25NpM4djFMHBmk8DubPAtL4YTOp7ou0e7HyJtwkVlWv8XMLURnrtgJQw==", - "dev": true, - "license": "ISC" - }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -1784,54 +1315,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", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2096,6 +1579,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", @@ -2237,16 +1738,6 @@ "node": ">= 0.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2993,24 +2484,11 @@ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "argparse": "^2.0.1" }, - "engines": { - "node": ">=6" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/json-buffer": { @@ -3034,19 +2512,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -3087,6 +2552,279 @@ "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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", @@ -3122,16 +2860,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3207,13 +2935,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3457,6 +3178,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", @@ -3581,16 +3315,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -3697,21 +3421,38 @@ "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/run-parallel": { @@ -4141,6 +3882,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", @@ -4264,37 +4030,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4306,50 +4041,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 +4109,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -4483,13 +4240,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index fc417bb3..c770be32 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,11 +17,11 @@ "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.3", + "@vitejs/plugin-react": "^6.0.2", "eslint": "^8.45.0", "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 +} From 2466c96bd1d9fbd8a0567ef33f47973325154ffb Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Wed, 10 Jun 2026 11:22:44 +0200 Subject: [PATCH 33/40] Run npm audit fix to resolve qs vulnerability --- backend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 35834289..8ae74d1b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1233,9 +1233,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" From 918b4786d7ca1af1fc055660a23cf204312c26d3 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 13:40:24 +0200 Subject: [PATCH 34/40] Specificerade JWT-algoritmen --- backend/middleware/auth.js | 2 +- backend/server.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index e3bdf47c..b0c422fb 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -7,7 +7,7 @@ export const authenticateUser = async (req, res, next) => { return res.status(401).json({ success: false, message: "No token provided" }) } try { - const decoded = jwt.verify(token, process.env.JWT_SECRET) + const decoded = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ["HS256"] }) //fas 3: Låser verifieringen till algoritmen HS256 så att tokens inte kan godkännas med "none"-algoritm eller annan algoritm. const user = await User.findById(decoded.userId) if (!user) { return res.status(401).json({ success: false, message: "User not found" }) diff --git a/backend/server.js b/backend/server.js index e9bd4d34..7f24d970 100644 --- a/backend/server.js +++ b/backend/server.js @@ -58,10 +58,11 @@ app.post("/register", async (req, res) => { const user = new User({ username: username.trim(), email, password: hashedPassword }) await user.save() + /// Anger algoritmen explicit så signering och verifiering inte kan glida isär ifall default-algoritmen i JWT-biblioteket ändras. const accessToken = jwt.sign( { userId: user._id, username: user.username }, process.env.JWT_SECRET, - { expiresIn: "2h" } + { expiresIn: "2h", algorithm: "HS256" } ) res.status(201).json({ From 38b4234b0d6eea232f0deac4d11b0811c7fe40cc Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 19:05:16 +0200 Subject: [PATCH 35/40] granskningsfasen --- backend/server.js | 4 ++-- frontend/src/App.jsx | 2 +- granskningsfasen.md | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/backend/server.js b/backend/server.js index 7f24d970..28e6c5a1 100644 --- a/backend/server.js +++ b/backend/server.js @@ -108,11 +108,11 @@ app.post("/login", loginLimiter, async (req, res) => { response: null, }) } - + /// fas 3: Anger algoritmen explicit så signering och verifiering inte kan glida isär ifall default-algoritmen i JWT-biblioteket ändras. const accessToken = jwt.sign( { userId: user._id, username: user.username }, process.env.JWT_SECRET, - { expiresIn: "2h" } + { expiresIn: "2h", algorithm: "HS256" } ) res.json({ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 011e67c7..f58bccf3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,7 +11,7 @@ export const App = () => { const [modal, setModal] = useState(null) const [error, setError] = useState(null) - //Säkerhetskrav 3: Nedan är ny fetchPosts variabel. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. + //Säkerhetskrav 3: Nedan är ny fetchPosts-funktion. När vi la till authenticateUser på GET /messages i backenden som en del av säkerhetskrav 3, att endast inloggade ska kunna se meddelanden, behövde frontenden också skicka med JWT-token i varje anrop, annars svarar backenden med 401 och inga meddelanden visas. Funktionen hämtar nu meddelanden från backenden och skickar med en Authorization-header som innehåller användarens JWT-token. Backenden använder token för att verifiera att användaren är inloggad innan den returnerar meddelandena. Utan token blockeras anropet av authenticateUser-middleware och inga meddelanden skickas tillbaka. const fetchPosts = () => { setLoading(true) fetch(`${BASE_URL}/messages`, { diff --git a/granskningsfasen.md b/granskningsfasen.md index 088f9718..a1a421d6 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -1 +1,39 @@ -# Inlämning 3 - Granskningsfasen \ No newline at end of file +# Inlämning 3 - Granskningsfasen + +Jag använde mig av CodeQL och Dependabot i GitHub för att granska koden. + +## CodeQL larmade om 9 problem i koden + +**CORS-konfiguration** 1 av CodeQL-larmen var CORS-sårbarheten som jag redan hittat själv i fas 2. Att CORS-konfigurationen var för slapp mappar jag mot OWASP 2025 A02: Security Misconfiguration. Det är åtgärdat genom att jag ändrade origin från "*" till "http://localhost:5173". I en deployad version skulle det ändras till min frontend-URL. + +**Rate limiting** 8 av 9 larm i CodeQL. Utan rate limiting på endpointsen kan en angripare skicka tusentals requests per sekund till API:et, vilket överlastar servern och gör tjänsten otillgänglig för riktiga användare (DoS). Detta mappar jag mot OWASP 2025: A04: Insecure Design. + +För att åtgärda detta skapade jag en ny middleware i rateLimiter.js, som jag döpte till generalLimiter som tillåter som mest 100 requests per IP under 15 minuter. + +Först satte jag den per endpoint, men larm kvarstod eftersom jag placerat den efter authenticateUser i middleware-ordningen. Ett databasanrop hann alltså ske innan limitern stoppade requesten. Jag la den därför globalt i stället. Det följer principen secure by default då alla endpoints, även nya, skyddas automatiskt utan att utvecklaren behöver komma ihåg att lägga till limitern per endpoint. Det följer också DRY-principen, eftersom skyddet definieras på ett ställe i stället för att upprepas. + +## Dependabot +Dependabot larmade om 16 potentiella sårbarheter som jag löste genom att uppdatera de olika biblioteken som var berörda. Övergripande mappar sårbarheterna mot OWASP 2025 A03: Software Supply Chain Failures som handlar om risker i kedjan av kod man förlitar sig på. + +**jsonwebtoken (3 larm).** Version 8.5.1 hade brister i hur tokens verifieras, varav en av de tre bristerna kunde ha en påverkan på vår app genom koden. +Den gamla versionen av biblioteket kunde under vissa förhållanden luras att godkänna en token som inte hade en signatur. För att kryphålet skulle kunna utnyttjas krävdes tre saker samtidigt: att ingen algoritm angavs i jwt.verify(), att en token utan signatur togs emot, och att JWT_SECRET var falsy (saknades eller laddades inte). Var alla tre uppfyllda kunde verifieringen falla tillbaka på algoritmen "none" och godkänna en oäkta token. I vår app var ingen algoritm angiven, och eftersom en angripare alltid kan skicka en signaturlös token, så hölls skyddet uppe bara av att JWT_SECRET var korrekt ifylld. Hade den saknats eller laddats fel hade kryphålet öppnats. + +De två övriga jwt-larmen rörde verifiering med asymmetriska nycklar och var inte aktuella för vår webapp. +Lösningen för alla tre larmen var att uppgradera till 9.0.0, där algoritmen "none" inte längre finns att falla tillbaka på. + +**tar (6 larm).** tar är ett uppackningsverktyg som låg flera led ner bland beroendena. Hashningsbiblioteket bcrypt använde ett verktyg som i sin tur använde tar för att packa upp filer vid installation. tar kontrollerade inte vart filerna i ett paket tog vägen, så ett illvilligt paket hade kunnat lägga skadliga filer på fel ställen på utvecklarens dator, och i värsta fall köra skadlig kod där. Dependabot kunde inte lösa larmen automatiskt, eftersom den lagade versionen av tar var nyare än vad mellanledet tillät, och Dependabot får inte bryta ett pakets versionskrav på egen hand. Jag löste det genom att uppgradera bcrypt till en version som inte längre använder tar. + +**Vite/esbuild – obehörig åtkomst till utvecklingsservern (5 larm).** Vites utvecklingsserver serverar projektets filer till webbläsaren och har regler för vad som inte får lämnas ut (t.ex. .env med hemligheter). Fem larm var olika sätt den kontrollen kunde kringgås. Gemensamt: data som borde varit skyddad kunde läsas av obehöriga. Vite körs bara under utveckling och följer inte med i den färdiga appen, så risken för användarna var låg, men det är viktigt att hemligheter i .env inte sprids. + +**launch-editor – kommandoinjektion (1 larm).** Vite har en funktion där man kan klicka på ett felmeddelande i webbläsaren för att öppna rätt fil i sin kodeditor, vilket sköts av verktyget launch-editor som kör ett kommando på datorn. På Windows kunde en manipulerad förfrågan injicera egna kommandon i det anropet, alltså få datorn att köra angriparens kod. + +Samtliga Vite-kopplade larm åtgärdades genom att jag uppgraderade Vite från v4 till v8. + +**qs (1 larm).** qs är ett paket som följer med Express och hanterar query-strängar. Dependabot larmade om att en bugg i funktionen qs.stringify under specifika inställningar kunde få servern att krascha (DoS). När jag granskade min kod såg jag dock att jag aldrig använder den sårbara kodvägen, så buggen var i praktiken inte exploaterbar hos vår app. Jag åtgärdade larmet ändå med npm audit fix, eftersom paketet körs i drift och den lagade versionen rymdes inom tillåtet versionsspann. + +## Sammanfattning dependabot +Efter att ha granskat min kod kan jag konstatera som koden ser ut nu var varken qs eller jsonwebtoken-sårbarheterna exploaterbara i praktiken: jag använder aldrig den sårbara qs-funktionen (qs.stringify med encodeValuesOnly), och jwt-kryphålet hölls stängt av att JWT_SECRET var satt. Larmen var alltså reella, men den faktiska risken för min app var låg. + +Samma relativt låga risk gäller även de övriga larmen. tar kördes bara vid installation, inte av min kod, och kunde bara utnyttjas om någon lyckades mata in ett manipulerat arkiv i nedladdningssteget, låg sannolikhet, men hög konsekvens (kodkörning på utvecklarens dator), vilket motiverade åtgärd. Två av Vite-larmen var Windows-specifika och därför inte aktuella på min Mac, medan de återstående Vite- och esbuild-larmen krävde att utvecklingsservern var igång och kunde nås av manipulerade förfrågningar, i praktiken genom att jag besökte en illvillig webbsida medan servern kördes. Det är en smal attackväg som bara var relevant under aktiv utveckling, inte i den färdiga appen. + +Samtliga dependabot-larm var reella, men den praktiska risken för att sårbarheterna skulle exploateras var låg, antingen på grund av att jag inte anropade sårbar kod (qs, jwt), att sårbarheten gällde annat operativsystem än mitt (Vite på Windows), samt smal attackväg genom installations- och utvecklingsmiljön (tar, esbuild) Att avgöra det krävde att jag gick från vad verktyget rapporterade till vad det faktiskt betydde för min kodbas och miljö. From 28a69e78b6ad999d9cc45c59a1c33db7b2d25740 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 22:49:44 +0200 Subject: [PATCH 36/40] granskningsfasen --- granskningsfasen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granskningsfasen.md b/granskningsfasen.md index a1a421d6..f48e81a2 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -6,7 +6,7 @@ Jag använde mig av CodeQL och Dependabot i GitHub för att granska koden. **CORS-konfiguration** 1 av CodeQL-larmen var CORS-sårbarheten som jag redan hittat själv i fas 2. Att CORS-konfigurationen var för slapp mappar jag mot OWASP 2025 A02: Security Misconfiguration. Det är åtgärdat genom att jag ändrade origin från "*" till "http://localhost:5173". I en deployad version skulle det ändras till min frontend-URL. -**Rate limiting** 8 av 9 larm i CodeQL. Utan rate limiting på endpointsen kan en angripare skicka tusentals requests per sekund till API:et, vilket överlastar servern och gör tjänsten otillgänglig för riktiga användare (DoS). Detta mappar jag mot OWASP 2025: A04: Insecure Design. +**Rate limiting** 8 av 9 larm i CodeQL. Utan rate limiting på endpointsen kan en angripare skicka tusentals requests per sekund till API:et, vilket överlastar servern och gör tjänsten otillgänglig för riktiga användare (DoS). Detta mappar jag mot OWASP 2025: A06: Insecure Design. För att åtgärda detta skapade jag en ny middleware i rateLimiter.js, som jag döpte till generalLimiter som tillåter som mest 100 requests per IP under 15 minuter. From 438d6371e5bba1a93fa52c88a19f979c79c9cb3e Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 22:49:56 +0200 Subject: [PATCH 37/40] granskningsfasen --- backend/middleware/rateLimiter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 3b786bd4..816d5aa4 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -9,7 +9,7 @@ export const loginLimiter = rateLimit({ // Säkerhetskrav 6. Här har jag lagt in en funktion loginLimiter som använder express-rate-limit för att begränsa antalet inloggningsförsök till 5 per 15 minuter. Sedan använder jag loginLimiter som middleware i server.js: app.post("/login") för att tillämpa denna begränsning på inloggningsförsöken. -// Efter att ha använt CodeQL och hittat sårbarheter la jag till ytterligare en limiter för att använda på övriga endpoints som gör databasanrop. Annars kan en angripare skicka tusentals requests per sekund och överlasta databas/server. Satte ett högre tak än loginLimiter eftersom 5 anrop på 15 minuter skulle göra appen oanvändbar på tex /messages GET/POST/PATCH/DELETE. +// fas 3: Efter att ha använt CodeQL och hittat sårbarheter la jag till ytterligare en limiter för att använda på övriga endpoints som gör databasanrop. Annars kan en angripare skicka tusentals requests per sekund och överlasta databas/server. Satte ett högre tak än loginLimiter eftersom 5 anrop på 15 minuter skulle göra appen oanvändbar på tex /messages GET/POST/PATCH/DELETE. export const generalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minuter max: 100, // 100 requests per 15 min per IP From 46466f82d57741371f4b26da658273be4e2b6cb0 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 22:54:47 +0200 Subject: [PATCH 38/40] granskningsfasen --- granskningsfasen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/granskningsfasen.md b/granskningsfasen.md index f48e81a2..e2022f75 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -16,7 +16,7 @@ Först satte jag den per endpoint, men larm kvarstod eftersom jag placerat den e Dependabot larmade om 16 potentiella sårbarheter som jag löste genom att uppdatera de olika biblioteken som var berörda. Övergripande mappar sårbarheterna mot OWASP 2025 A03: Software Supply Chain Failures som handlar om risker i kedjan av kod man förlitar sig på. **jsonwebtoken (3 larm).** Version 8.5.1 hade brister i hur tokens verifieras, varav en av de tre bristerna kunde ha en påverkan på vår app genom koden. -Den gamla versionen av biblioteket kunde under vissa förhållanden luras att godkänna en token som inte hade en signatur. För att kryphålet skulle kunna utnyttjas krävdes tre saker samtidigt: att ingen algoritm angavs i jwt.verify(), att en token utan signatur togs emot, och att JWT_SECRET var falsy (saknades eller laddades inte). Var alla tre uppfyllda kunde verifieringen falla tillbaka på algoritmen "none" och godkänna en oäkta token. I vår app var ingen algoritm angiven, och eftersom en angripare alltid kan skicka en signaturlös token, så hölls skyddet uppe bara av att JWT_SECRET var korrekt ifylld. Hade den saknats eller laddats fel hade kryphålet öppnats. +Den gamla versionen av biblioteket kunde under vissa förhållanden luras att godkänna en token som inte hade en signatur. För att kryphålet skulle kunna utnyttjas krävdes tre saker samtidigt: att ingen algoritm angavs i jwt.verify(), att en token utan signatur togs emot, och att JWT_SECRET var falsy (saknades eller laddades inte). Var alla tre uppfyllda kunde verifieringen falla tillbaka på algoritmen "none" och godkänna en oäkta token. I vår app var ingen algoritm angiven, och eftersom en angripare alltid kan skicka en signaturlös token, så hölls skyddet uppe bara av att JWT_SECRET var korrekt ifylld. Hade den saknats eller laddats fel hade kryphålet öppnats. Konsekvensen hade varit ett autentiseringshaveri, och kunnat mappas mot OWASP A07: Authentication Failures. De två övriga jwt-larmen rörde verifiering med asymmetriska nycklar och var inte aktuella för vår webapp. Lösningen för alla tre larmen var att uppgradera till 9.0.0, där algoritmen "none" inte längre finns att falla tillbaka på. From bea045f7c04ef005057d173922d3a60fe735ae0a Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Thu, 11 Jun 2026 23:42:52 +0200 Subject: [PATCH 39/40] granskningsfasen --- granskningsfasen.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/granskningsfasen.md b/granskningsfasen.md index e2022f75..0257942f 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -32,8 +32,8 @@ Samtliga Vite-kopplade larm åtgärdades genom att jag uppgraderade Vite från v **qs (1 larm).** qs är ett paket som följer med Express och hanterar query-strängar. Dependabot larmade om att en bugg i funktionen qs.stringify under specifika inställningar kunde få servern att krascha (DoS). När jag granskade min kod såg jag dock att jag aldrig använder den sårbara kodvägen, så buggen var i praktiken inte exploaterbar hos vår app. Jag åtgärdade larmet ändå med npm audit fix, eftersom paketet körs i drift och den lagade versionen rymdes inom tillåtet versionsspann. ## Sammanfattning dependabot -Efter att ha granskat min kod kan jag konstatera som koden ser ut nu var varken qs eller jsonwebtoken-sårbarheterna exploaterbara i praktiken: jag använder aldrig den sårbara qs-funktionen (qs.stringify med encodeValuesOnly), och jwt-kryphålet hölls stängt av att JWT_SECRET var satt. Larmen var alltså reella, men den faktiska risken för min app var låg. +Efter att ha granskat min kod kan jag konstatera att som koden ser ut nu var varken qs eller jsonwebtoken-sårbarheterna exploaterbara i praktiken: jag använder aldrig den sårbara qs-funktionen (qs.stringify med encodeValuesOnly), och jwt-kryphålet hölls stängt av att JWT_SECRET var satt. Larmen var alltså reella, men den faktiska risken för min app var låg. -Samma relativt låga risk gäller även de övriga larmen. tar kördes bara vid installation, inte av min kod, och kunde bara utnyttjas om någon lyckades mata in ett manipulerat arkiv i nedladdningssteget, låg sannolikhet, men hög konsekvens (kodkörning på utvecklarens dator), vilket motiverade åtgärd. Två av Vite-larmen var Windows-specifika och därför inte aktuella på min Mac, medan de återstående Vite- och esbuild-larmen krävde att utvecklingsservern var igång och kunde nås av manipulerade förfrågningar, i praktiken genom att jag besökte en illvillig webbsida medan servern kördes. Det är en smal attackväg som bara var relevant under aktiv utveckling, inte i den färdiga appen. +tar körs bara vid installation, inte av min kod, och kan bara utnyttjas om ett manipulerat arkiv matas in i nedladdningssteget. En riktad attack mot just min nedladdning är osannolik, men leveranskedjeattacker behöver inte vara riktade: om någon planterar skadlig kod i en populär källa drabbas alla som laddar ner därifrån, oavsett hur litet projektet är. Den sortens attack har hänt flera gånger i verkligheten, och konsekvensen hade varit allvarlig (kodkörning på utvecklarens dator), vilket motiverade åtgärd trots att mitt enskilda projekt är ett osannolikt mål. -Samtliga dependabot-larm var reella, men den praktiska risken för att sårbarheterna skulle exploateras var låg, antingen på grund av att jag inte anropade sårbar kod (qs, jwt), att sårbarheten gällde annat operativsystem än mitt (Vite på Windows), samt smal attackväg genom installations- och utvecklingsmiljön (tar, esbuild) Att avgöra det krävde att jag gick från vad verktyget rapporterade till vad det faktiskt betydde för min kodbas och miljö. +Två av Vite-larmen var Windows-specifika och därför inte aktuella på min Mac, medan de återstående Vite- och esbuild-larmen krävde att utvecklingsservern var igång och kunde nås av manipulerade förfrågningar, i praktiken genom att jag besöker en illvillig webbsida medan servern kördes. Det är en smal attackväg som bara är relevant under aktiv utveckling, inte i den färdiga appen. \ No newline at end of file From f619bb5720ba766710f8edad0933cb3bef5477e1 Mon Sep 17 00:00:00 2001 From: Karin Isaksson Date: Fri, 12 Jun 2026 14:50:39 +0200 Subject: [PATCH 40/40] slutsats --- granskningsfasen.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/granskningsfasen.md b/granskningsfasen.md index 0257942f..d9ea8bfb 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -36,4 +36,8 @@ Efter att ha granskat min kod kan jag konstatera att som koden ser ut nu var var tar körs bara vid installation, inte av min kod, och kan bara utnyttjas om ett manipulerat arkiv matas in i nedladdningssteget. En riktad attack mot just min nedladdning är osannolik, men leveranskedjeattacker behöver inte vara riktade: om någon planterar skadlig kod i en populär källa drabbas alla som laddar ner därifrån, oavsett hur litet projektet är. Den sortens attack har hänt flera gånger i verkligheten, och konsekvensen hade varit allvarlig (kodkörning på utvecklarens dator), vilket motiverade åtgärd trots att mitt enskilda projekt är ett osannolikt mål. -Två av Vite-larmen var Windows-specifika och därför inte aktuella på min Mac, medan de återstående Vite- och esbuild-larmen krävde att utvecklingsservern var igång och kunde nås av manipulerade förfrågningar, i praktiken genom att jag besöker en illvillig webbsida medan servern kördes. Det är en smal attackväg som bara är relevant under aktiv utveckling, inte i den färdiga appen. \ No newline at end of file +Två av Vite-larmen var Windows-specifika och därför inte aktuella på min Mac, medan de återstående Vite- och esbuild-larmen krävde att utvecklingsservern var igång och kunde nås av manipulerade förfrågningar, i praktiken genom att jag besöker en illvillig webbsida medan servern kördes. Det är en smal attackväg som bara är relevant under aktiv utveckling, inte i den färdiga appen. + +## Slutsats + +Både CodeQL och Dependabot larmade om sårbarheter som jag inte hittat själv, vid manuell granskning (där jag tog hjälp av Claude Code). Det känns viktigt att kunna använda sådana verktyg, speciellt när utvecklingscommunityt är byggt på att man använder andras bibliotek. Det hade varit väldigt svårt att hålla koll på vilka sårbarheter man drar in annars. Jag löste alla dependabot-larmen, men var noga med att testa alla appens funktioner efter varje uppdatering för att inget skulle gå sönder.