diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..fa31f305 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 872d5f6c..30d878c4 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,7 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +# Environment variables file +.env + diff --git a/README.md b/README.md index d6aeeb20..8af38664 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# yh-message-app-fullstack \ No newline at end of file +# yh-message-app-fullstack +Test test diff --git a/backend/config/db.js b/backend/config/db.js index a4257e74..617714cb 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,13 +1,12 @@ import mongoose from "mongoose" -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/messages" - -mongoose.connect(mongoUrl) - -mongoose.connection.once("open", () => { - console.log("Connected to MongoDB") -}) - -mongoose.connection.on("error", err => { - console.error("connection error:", err) -}) +export const connectDB = async () => { + try { + const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/messages" + await mongoose.connect(mongoUrl) + console.log("Connected to MongoDB") + } catch (err) { + console.error("connection error:", err) + process.exit(1) + } +} \ No newline at end of file diff --git a/backend/middleware/loginLimiter.js b/backend/middleware/loginLimiter.js new file mode 100644 index 00000000..a55443c3 --- /dev/null +++ b/backend/middleware/loginLimiter.js @@ -0,0 +1,10 @@ +import rateLimit from "express-rate-limit" // Express-rate-limit används för att begränsa antalet inloggningsförsök från en specifik IP-adress under en viss tidsperiod. Detta hjälper till att skydda mot brute-force attacker där en angripare försöker gissa lösenord genom att göra många inloggningsförsök. + +export const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minuter + max: 5, + message: { + success: false, + message: "För många inloggningsförsök. Försök igen om 15 minuter." + } +}); \ No newline at end of file diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js new file mode 100644 index 00000000..33b26db3 --- /dev/null +++ b/backend/middleware/rateLimiter.js @@ -0,0 +1,7 @@ +import rateLimit from "express-rate-limit" + +export const globalLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 100, + message: "Too many requests from this IP, please try again later." +}) diff --git a/backend/models/User.js b/backend/models/User.js index 266a60b4..66905d49 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -5,7 +5,7 @@ const userSchema = new mongoose.Schema({ type: String, required: true, unique: true, - minlength: 2, + maxlength: 25, // maxlängd på användarnamn satt till 25 tecken för att undvika överdrivet långa användarnamn trim: true, }, email: { @@ -13,10 +13,13 @@ const userSchema = new mongoose.Schema({ required: true, unique: true, lowercase: true, + maxlength: 254, // maxlängd på email satt till 254 tecken enligt RFC 5321 }, password: { type: String, required: true, + minlength: 10, // minlängd på lösenord satt till 10 tecken för att uppmuntra starka lösenord + maxlength: 64, // maxlängd på lösenord satt till 64 tecken för att undvika överdrivet långa lösenord }, }) diff --git a/backend/package-lock.json b/backend/package-lock.json index d8c4b3cd..7b0311f7 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-validator": "^7.3.2", "helmet": "^7.1.0", "jsonwebtoken": "8.5.1", "mongoose": "^8.4.0", @@ -619,6 +620,19 @@ "node": ">=10" } }, + "node_modules/express-validator": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.2.tgz", + "integrity": "sha512-ctLw1Vl6dXVH62dIQMDdTAQkrh480mkFuG6/SGXOaVlwPNukhRAe7EgJIMJ2TSAni8iwHBRp530zAZE5ZPF2IA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.18.1", + "validator": "~13.15.23" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1108,6 +1122,12 @@ "node": ">=12.0.0" } }, + "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/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2156,6 +2176,15 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index 1f47ffb6..35bc310a 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-validator": "^7.3.2", "helmet": "^7.1.0", "jsonwebtoken": "8.5.1", "mongoose": "^8.4.0", diff --git a/backend/server.js b/backend/server.js index c8d0c218..e927e690 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,6 @@ import "dotenv/config" -import helmet from "helmet" -import cors from "cors" +import helmet from "helmet" // Helmet hjälper till att säkra Express-applikationen genom att ställa in olika HTTP-headers som skyddar mot vanliga sårbarheter. +import cors from "cors" //ändra så bara vår frontend kan göra requests, istället för att tillåta alla origins. Det ökar säkerheten genom att begränsa vilka domäner som kan interagera med vår backend. import express from "express" import mongoose from "mongoose" import bcrypt from "bcrypt" @@ -8,8 +8,11 @@ import jwt from "jsonwebtoken" import { Message } from "./models/Message.js" import { User } from "./models/User.js" import { authenticateUser } from "./middleware/auth.js" -import "./config/db.js" +import { loginLimiter } from "./middleware/loginLimiter.js" // Importerade loginLimiter-middleware för att skydda inloggningsendpointen mot brute-force attacker. +import { connectDB } from "./config/db.js" // Importerar funktionen för att starta databasen stabilt import listEndpoints from "express-list-endpoints" +import { body, param, validationResult } from "express-validator" // Importerade express-validator för ökad validering av indata +import { globalLimiter } from "./middleware/rateLimiter.js" // Global limiter som skyddar alla endpoints mot överbelastningsattacker (DoS) och fixar CodeQL-varningarna if (!process.env.JWT_SECRET) throw new Error("JWT_SECRET is not set in .env") @@ -17,42 +20,50 @@ const PORT = process.env.PORT || "3000" const app = express() app.use(helmet()) app.use(cors({ - origin: "*", + origin: process.env.FRONTEND_URL, // Endast tillåta requests från vår frontend, istället för att tillåta alla origins. Det ökar säkerheten genom att begränsa vilka domäner som kan interagera med vår backend. })) app.use(express.json()) +// Vi föreslår att säkra session-tokeln genom att använda HttpOnly cookies istället för att skicka den i JSON-responsen, vilket minskar risken för XSS-attacker. Det här innebär större ändringar i övrig kod för att hantera autentisering och session, vi har valt att inte genomföra detta själva. + +app.use(globalLimiter) // Använder globalLimiter som middleware för att skydda alla endpoints mot överbelastningsattacker (DoS) och fixar CodeQL-varningarna app.get("/", (req, res) => { res.send(listEndpoints(app)) }) -app.post("/register", async (req, res) => { +app.post("/register", loginLimiter, [ // Validering av indata för register adderad via Express-validator + body("username").isLength({ min: 6, max: 25 }).withMessage("Username must be between 6 and 25 characters"), // Adderade validering för användarnamn + body("email").isEmail().normalizeEmail(), // Validerar att email är i korrekt format och normaliserar det + body("password").isLength({ min: 10, max: 64 }).withMessage("Password must be between 10 and 64 characters") // Adderade validering för lösenordslängd +], async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ success: false, message: "Failed to register. Check your user name and password.", errors: errors.array() }) // Förenklade felmeddelandet och inkluderade detaljerad information om valideringsfel i svaret. + } + try { const { email, password, username } = req.body - if (!username || username.trim().length < 2) { - return res.status(400).json({ success: false, message: "Username must be at least 2 characters" }) - } - const existingUser = await User.findOne({ - $or: [{ email: email.toLowerCase() }, { username: username.trim() }] + $or: [{ email: email }, { username: username.trim() }] }) if (existingUser) { - const field = existingUser.email === email.toLowerCase() ? "email" : "username" + const field = existingUser.email === email ? "email" : "username" return res.status(400).json({ success: false, message: `A user with this ${field} already exists` }) } - const hashedPassword = await bcrypt.hash(password, 10) - const user = new User({ username: username.trim(), email, password: hashedPassword }) - await user.save() + const hashedPassword = await bcrypt.hash(password, 10) + const user = new User({ username: username.trim(), email, password: hashedPassword }) + await user.save() // Säkerställer att användaren sparas i databasen innan vi genererar token och skickar svar, vilket minskar risken för att en token genereras för en användare som inte faktiskt skapats på grund av ett databasfel eller liknande problem. const accessToken = jwt.sign( { userId: user._id, username: user.username }, process.env.JWT_SECRET, - { expiresIn: "2h" } + { expiresIn: "1h" } // Sänkte token expiration time till 1 timme för ökad säkerhet ) res.status(201).json({ @@ -64,16 +75,24 @@ app.post("/register", async (req, res) => { accessToken, }, }) - } catch (error) { + } catch (error) { + console.error(error) // Loggar det faktiska felet i serverns konsol res.status(400).json({ success: false, - message: "Could not create user", - error: error, + message: "Something went wrong", // Förenklade felmeddelandet som skickas till klienten för att inte exponera potentiellt känslig information. }) + } +}) + +app.post("/login", loginLimiter, [ // Validering av indata för login adderad via Express-validator + body("login").trim().notEmpty().withMessage("Username or email is required"), + body("password").notEmpty().withMessage("Password is required") +], async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ success: false, message: "Validation failed", errors: errors.array() }) } -}) -app.post("/login", async (req, res) => { try { const { login, password } = req.body const user = await User.findOne({ @@ -100,7 +119,7 @@ app.post("/login", async (req, res) => { const accessToken = jwt.sign( { userId: user._id, username: user.username }, process.env.JWT_SECRET, - { expiresIn: "2h" } + { expiresIn: "1h" } //Sänkte token expiration time till 1 timme för ökad säkerhet ) res.json({ @@ -113,17 +132,18 @@ app.post("/login", async (req, res) => { }, }) } catch (error) { + console.error(error) //tog bort detaljerad felinformation från svaret för att inte exponera potentiellt känslig information res.status(500).json({ success: false, message: "Something went wrong", - error: error, }) } }) -const isValidId = (id) => mongoose.Types.ObjectId.isValid(id) -app.get("/messages", async (req, res) => { + + +app.get("/messages", authenticateUser, async (req, res) => { //Lade till authenticateUser för att säkerställa att endast inloggade användare kan hämta meddelanden. try { const messages = await Message.find() .sort({ createdAt: "desc" }) @@ -136,9 +156,17 @@ app.get("/messages", async (req, res) => { } }) -app.post("/messages", authenticateUser, async (req, res) => { - const message = new Message({ message: req.body.message, user: req.user._id }) +app.post("/messages", [ + authenticateUser, + body("message").trim().isLength({ min: 1, max: 250 }).withMessage("Message must be between 1 and 250 characters") +], async (req, res) => { + const errors = validationResult(req) + if (!errors.isEmpty()) { +return res.status(400).json({ success: false, error: "Message must be between 1 and 250 characters", errors: errors.array() }) //Adderade mer detaljerad information om valideringsfel i svaret. + } + try { + const message = new Message({ message: req.body.message, user: req.user._id }) const saved = await message.save() res.status(201).json(saved) } catch (err) { @@ -146,13 +174,21 @@ app.post("/messages", 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" }) +app.patch("/messages/:id", [ + authenticateUser, + param("id").isMongoId().withMessage("Invalid database ID"), + body("editedMessage").trim().isLength({ min: 1, max: 250 }).withMessage("Edited message must be between 1 and 250 characters") +], async (req, res) => { + + const errors = validationResult(req) + if (!errors.isEmpty()) { + return res.status(400).json({ success: false, error: "Edited message must be between 1 and 250 characters", errors: errors.array() }) //Adderade mer detaljerad information om valideringsfel i svaret. + } try { const message = await Message.findById(req.params.id) if (!message) return res.status(404).json({ error: "Message not found" }) - if (message.user.toString() !== req.user._id.toString()) { + if (!message.user || message.user.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "You can only edit your own messages" }) } @@ -165,11 +201,23 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { } }) -app.delete("/messages/:id", async (req, res) => { - if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" }) +app.delete("/messages/:id", [ + authenticateUser, + param("id").isMongoId().withMessage("Invalid database ID") +], async (req, res) => { + + const errors = validationResult(req) + if (!errors.isEmpty()) { +return res.status(400).json({ success: false, error: "Invalid database ID", errors: errors.array() }) //Adderade mer detaljerad information om valideringsfel i svaret. +} try { const message = await Message.findById(req.params.id) if (!message) return res.status(404).json({ error: "Message not found" }) + + if (!message.user || 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) { @@ -177,6 +225,8 @@ app.delete("/messages/:id", async (req, res) => { } }) +await connectDB() + app.listen(PORT, () => { console.log(`Listening on port ${PORT}`) -}) +}) \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index fc417bb3..8b43954e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --host", + "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" @@ -24,4 +24,4 @@ "eslint-plugin-react-refresh": "^0.4.3", "vite": "^4.4.5" } -} \ No newline at end of file +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c668778f..3d8bbaf3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -12,19 +12,43 @@ export const App = () => { const [error, setError] = useState(null) const fetchPosts = () => { + // Om det inte finns någon inloggad användare eller token, avbryt hämtningen direkt + if (!user?.response?.accessToken) { + setLoading(false) + return + } + setLoading(true) - fetch(`${BASE_URL}/messages`) - .then((res) => res.json()) - .then((data) => setMessageList(data)) + fetch(`${BASE_URL}/messages`, { + headers: { + // Skickar med användarens JWT-token till backend för att få godkännt att hämta meddelanden + Authorization: `Bearer ${user.response.accessToken}`, + }, + }) + .then((res) => { + if (res.status === 401) { + handleUnauthorized() + return [] + } + return res.json() + }) + .then((data) => { + if (Array.isArray(data)) { + setMessageList(data) + } + }) .catch((error) => console.error(error)) .finally(() => setLoading(false)) } + useEffect(() => { + // Triggar en hämtning så fort komponenten laddas eller när 'user' ändras (t.ex. vid inloggning) fetchPosts() - }, []) + }, [user]) const addNewPost = (newMessage) => { - setMessageList([newMessage, ...messageList]) + setMessageList((prevList) => [newMessage, ...prevList]) //prevList säkerställer att inga meddelanden försvinner om användaren klickar snabbt + } const handleUnauthorized = () => { @@ -56,16 +80,15 @@ export const App = () => { onClick={() => setModal("register")} className="auth-button" > - Register - - - )} + Register + + + )} {modal && ( setModal(null)} onSuccess={(data) => { - console.log("User logged in:", data) setUser(data) setModal(null) }} diff --git a/frontend/src/api.js b/frontend/src/api.js index ee6f3531..c5ed351c 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1 +1,3 @@ -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" diff --git a/frontend/src/components/AuthModal.jsx b/frontend/src/components/AuthModal.jsx index 3d25f46b..94c1ef7b 100644 --- a/frontend/src/components/AuthModal.jsx +++ b/frontend/src/components/AuthModal.jsx @@ -4,10 +4,10 @@ import { BASE_URL } from "../api" export const AuthModal = ({ mode, onClose, onSuccess }) => { const [username, setUsername] = useState("") const [email, setEmail] = useState("") - const [login, setLogin] = useState("") + // Initierar login med username för att säkerställa att inloggningsfältet + // skickar rätt data (login-nyckel) till backend för verifiering. + const [login, setLogin] = useState(username) const [password, setPassword] = useState("") - const [error, setError] = useState(null) - const [submitting, setSubmitting] = useState(false) const handleSubmit = async (e) => { e.preventDefault() diff --git a/frontend/src/components/SingleMessage.jsx b/frontend/src/components/SingleMessage.jsx index 50d2274b..08aa05a8 100644 --- a/frontend/src/components/SingleMessage.jsx +++ b/frontend/src/components/SingleMessage.jsx @@ -22,7 +22,9 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => return } - await fetchPosts() + if (res.ok) { + await fetchPosts() + } } catch (error) { console.error(error) } @@ -83,7 +85,9 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) => )}
- + {isOwner && ( //endast visa delete-knappen för ägaren av meddelandet + + )} {isOwner && !isEditing && ( @@ -113,4 +117,4 @@ export const SingleMessage = ({ message, user, onUnauthorized, fetchPosts }) =>
) -} +} \ No newline at end of file diff --git a/granskningsfasen.md b/granskningsfasen.md index 088f9718..11b2903e 100644 --- a/granskningsfasen.md +++ b/granskningsfasen.md @@ -1 +1,49 @@ -# Inlämning 3 - Granskningsfasen \ No newline at end of file +# Inlämning 3 - Granskningsfasen + +Presentation på figma: +https://www.figma.com/board/UitaJT4H8hl28KNGnIGbRr/Projekt-%22s%C3%A4ker-systemutveckling%22?node-id=0-1&t=TnLGOxraNQkjn304-1 + +Vi genomförde säkerhetsscanning med CodeQL och Dependabot via GitHub. De två verktygen identifierade kritiska sårbarheter inom två huvudområden: applikationslogik (brute force och överbelastning) samt externa beroenden (sårbarheter i tredjepartsbibliotek). + +Vi kommer här redovisa för vad dessa sårbarheter skulle innebära, såväl som potentiella konsekvenser och resonemanget bakom våra implementerade åtgärder. + +CodeQL: Problemformulering och risker +När vi körde CodeQL hittade vi problemet att vår app inte hade några begränsningar för hur många gånger någon kunde skicka anrop till servern. Det kallas rate limiting och utan det var vi sårbara för två typer av attacker: +- Brute force-attacker +- Denial of Service (DoS) +Hur vi resonerade kring åtgärder: +För att skydda oss mot dessa hot valde vi att bygga upp ett skydd i flera lager via vår middleware-fil (ratelimiter.js) Tanken är att om ett lager kringgås finns det alltid ett till. + +Vi åtgärdade med ett Globalt skydd (globalLimiter): Vi satte ett tak för hur många anrop en och samma IP-adress får göra, oavsett vilken del av appen de försöker nå. Det skyddar hela systemet mot DoS-attacker. +Bristen vi åtgärdade återfinns i två av OWASP:s mest välkända säkerhetskategorier: +OWASP: A04:2021-Insecure Design, eftersom avsaknad av rate limiting är ett designbeslut som öppnar upp för attacker +OWASP: A05:2021-Security Misconfiguration, eftersom en server utan trafikbegränsningar är en vanlig och allvarlig säkerhetsbrist + +Sen har vi riktat skydd (loginLimiter): Inloggning och registrering är extra känsligt, där behövs ett striktare skydd. Vi la därför till loginLimiter specifikt på dessa ställen. Efter ett visst antal misslyckade försök blockeras IP-adressen automatiskt, vilket gör att brute force-attacker i princip omöjliga eller i alla fall avsevärt försvårade. +Kopplingen till OWASP: +A04:2021 – Insecure Design, eftersom avsaknad av riktad begränsning på känsliga endpoints möjliggör brute force-attacker +A05:2021 – Security Misconfiguration, eftersom autentiseringsendpoints alltid bör ha striktare säkerhetsinställningar. + +Sen till fel ordning i koden (user.save()): Vi hittade också ett problem i hur registreringen fungerade – koden skapade en inloggningstoken innan vi var säkra på att användaren faktiskt sparas i databasen. Det betyder att någon i teorin kunde ha en giltig token för ett konto som inte existerar. Vi fixade detta genom att lägga till await user save() så att databasen alltid sparar användaren först. +Kopplingen till OWASP: +A07:2021 – Identification and Authentication Failures, eftersom fel ordning i autentiseringsflödet kan leda till ogiltiga sessioner och komprometterad identitetshantering. + +Dependabot-fynd: Problemformulering och risker +Via vår scan med Dependabot identifierade vi 16 sårbarheter i projektets dependencies. Det visar på ett tydligt sätt vilka risker som finns med externa bibliotek, där sårbarheter “ärvs” in i egna system. Nedan följer de instanser där sårbarheter hittades: +- tar (via bcrypt): Sårbarheter i arkivhanteringsbibliotek som tar handlar ofta om filöverskrivning (Directory Traversal) eller Denial of Service vid parsning av skadliga filer. Eftersom bcrypt använder detta sub-bibliotek kan applikationen drabbas indirekt vid hantering av krypteringsresurser. +- jsonwebtoken: Äldre versioner av detta bibliotek har kända brister gällande verifiering av signaturer (till exempel att acceptera algoritmen "none"), vilket tillåter angripare att förfalska tokens och kringgå autentiseringen helt. +- Vite, esbuild och qs: Sårbarheter i byggverktyg (Vite, esbuild) kan leda till kodexekvering på utvecklares maskiner eller i CI/CD-pipelines. Brist i qs kan leda till prototyp-förorening (Prototype Pollution), vilket kan krascha applikationen eller manipulera objektstrukturer i JavaScript. + +Resonemang kring åtgärder +OBS! Vi har valt att inte själva implementera dessa patchningar, då vi inte besitter tillräcklig kunskap för att hantera potentiella konflikter eller krascher som kan uppstå vid en sådan åtgärd. Därför ombeds IT-avdelningen att äga ansvaret över samtlig patchning. + +Vi rekommenderar att uppdatera samtliga dependencies till patchade versioner som den mest effektiva strategin. Att ligga kvar på utdaterade versioner innebär att vi accepterar kända sårbarheter (CVE-id) som ligger öppet tillgängliga i publika databaser, vilket gör dem enkla att utnyttja för exempelvis automatiserade attackverktyg. + +Uppdateringen av tar till 7.5.11 och jsonwebtoken till 9.0.0 skulle stänga kända säkerhetshål direkt vid källan. +Genom att uppdatera utvecklingsverktyg (Vite, esbuild) skulle vi även säkra leveranskedjan (Supply Chain Security), vilket kan förhindra att skadlig kod injiceras under kompileringsfasen. +OWASP: A06:2021-Vulnerable and Outdated Components + +Sammanfattning av verksamhetskonsekvenser +Att ignorera de sårbarheter som identifierades hade kunnat leda till obehörig datatillgång, stulna användarkonton och kraschande system. Genom att kombinera ovan kodändringar (CodeQL-åtgärder) med rekommenderad regelbunden patchhantering (Dependabot-åtgärder) skulle vi uppnå en högre säkerhetsnivå (Defense in Depth). Systemet skulle därmed vara både mer motståndskraftigt mot riktade externa attacker och bättre skyddat mot sårbarheter i den egna leveranskedjan. + +Utifrån våra förutsättningar har vi i fas 1 satt upp säkerhetskrav. Vi har åtgärdat en del av dessa i koden och hänvisat till rekommenderade åtgärder som vi ansett varit för komplexa för oss. Vi har även använt oss av verktyg för att identifiera sårbarheter som är ett bra komplement. Vi anser trots dessa åtgärder att vi inte kan vara helt säkra på applikationens säkerhet och rekommenderar att utvecklare granskar koden och genomför regelbunden utvärdering av applikationens säkerhetsposition. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f8b3722f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,933 @@ +{ + "name": "yh-message-app-fullstack", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "express-rate-limit": "^8.5.2" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "peer": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "peer": true + }, + "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", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "peer": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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", + "peer": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "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", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "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", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "peer": true + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "peer": true, + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "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", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ed511c4b --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "express-rate-limit": "^8.5.2" + } +} diff --git a/planeringsfasen.md b/planeringsfasen.md index 661cae0e..cd3705ac 100644 --- a/planeringsfasen.md +++ b/planeringsfasen.md @@ -1 +1,3 @@ -# Inlämning 1 - Planeringsfasen \ No newline at end of file +# Inlämning 1 - Planeringsfasen + +https://www.figma.com/board/UitaJT4H8hl28KNGnIGbRr/Projekt-%22s%C3%A4ker-systemutveckling%22?node-id=0-1&p=f&t=lcQEilGf5fm5rGqb-0 \ No newline at end of file