diff --git a/HotmodelleringFas1.png b/HotmodelleringFas1.png new file mode 100644 index 00000000..30e95f9f Binary files /dev/null and b/HotmodelleringFas1.png differ diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index e3bdf47c..2d2ce92a 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -7,9 +7,12 @@ export const authenticateUser = async (req, res, next) => { return res.status(401).json({ success: false, message: "No token provided" }) } try { + // POSITIVT: Verifierar token mot hemlig nyckel (JWT_SECRET) innan åtkomst godkänns const decoded = jwt.verify(token, process.env.JWT_SECRET) const user = await User.findById(decoded.userId) - if (!user) { + // SÄKERHETSBRIST (User Enumeration): Filen returnerar olika felmeddelanden beroende på varför autentisering misslyckas ("No token provided", "User not found", "Invalid token"). + // Kan avslöja info för angriparen om varför åtkomst nekades. Enligt säkerhetskrav från fas 1 bör samma generiska felmeddelande returneras oavsett orsak. + if (!user) { return res.status(401).json({ success: false, message: "User not found" }) } req.user = user diff --git a/backend/models/Message.js b/backend/models/Message.js index dbca0001..58717bcc 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -2,10 +2,13 @@ import mongoose from "mongoose" const messageSchema = new mongoose.Schema({ message: { + // SÄKERHETSBRIST (XSS/Injection): Meddelandefältet saknar validering och sanering av innehåll. Skadlig kod kan injiceras och lagras i databasen. + // SÄKERHETSBRIST (BAC): Modellen saknar maxlength på meddelandefältet vilket innebär att extremt långa meddelanden kan skickas. Behörighetskontroll, dvs vem som får läsa, ändra eller ta bort ett meddelande, hanteras inte här utan måste implementeras i API-logiken. type: String, required: true }, user: { + // POSITIVT: Meddelandet är kopplat till en specifik användare via user-fältet, en bra grund för behörighetskontroll. type: mongoose.Schema.Types.ObjectId, ref: "User", }, diff --git a/backend/server.js b/backend/server.js index c8d0c218..2365ace0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -165,6 +165,7 @@ app.patch("/messages/:id", authenticateUser, async (req, res) => { } }) +// SÄKERHETSBRIST (BAC): DELETE-endpointen saknar autentisering (authenticateUser), vem som helst kan ta bort vilket meddelande som helst utan att vara inloggad. app.delete("/messages/:id", async (req, res) => { if (!isValidId(req.params.id)) return res.status(400).json({ error: "Invalid message ID" }) try { diff --git a/fas3-sammanfattning.md b/fas3-sammanfattning.md new file mode 100644 index 00000000..89f8b066 --- /dev/null +++ b/fas3-sammanfattning.md @@ -0,0 +1,20 @@ +Fas 3 – Granskning: Sammanfattning + +Verktyg som använts: +CodeQL och Dependabot via GitHub. + +Fynd: +CodeQL identifierade 8 alerts i backend/server.js, 7 endpoints saknar rate limiting (High) och en tillåtande CORS-konfiguration (Medium). +Dependabot identifierade 16 alerts, varav JWT-sårbarheterna (jsonwebtoken) är de mest kritiska då det är ett direkt beroende aktivt i appen. + +Koppling till OWASP Top 10: +Saknad rate limiting → A07: Identification and Authentication Failures +JWT-sårbarheter → A02: Cryptographic Failures + +Åtgärdsförslag: +- Installera express-rate-limit och begränsa känsliga endpoints +- Uppdatera jsonwebtoken till senaste versionen + + +Slutsats: +Manuell granskning och automatiserade verktyg kompletterar varandra. Verktygen fångade konfigurationsbrister som missades i Fas 2, medan den manuella granskningen identifierade logikfel som verktygen inte upptäckte. diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c668778f..ad6e42d0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,7 @@ export const App = () => { const fetchPosts = () => { setLoading(true) + // SÄKERHETSBRIST (BAC): /messages-endpointen hämtas utan autentisering. Vem som helst kan se alla meddelanden utan att vara inloggad. fetch(`${BASE_URL}/messages`) .then((res) => res.json()) .then((data) => setMessageList(data)) @@ -27,11 +28,13 @@ export const App = () => { setMessageList([newMessage, ...messageList]) } + //POSITIVT: handleUnauthorized nollställer användarsessionen när token går ut. Logout-funktionen finns och nollställer användaren korrekt. const handleUnauthorized = () => { setUser(null) + // SÄKERHETSBRIST (User Enumeration): Felmeddelandet "Your session has expired, please log in again" avslöjar info om sessionshanteringen för användaren. setError("Your session has expired, please log in again") } - + return ( <> {user ? ( @@ -64,10 +67,11 @@ export const App = () => { setModal(null)} - onSuccess={(data) => { + onSuccess={(data) => { + // SÄKERHETSBRIST (Information Disclosure): console.log("User logged in:", data) loggar användardata i webbläsarens konsol vilket är synligt för vem som helst som öppnar dev-verktygen i webbläsaren. Bör tas bort i skarp miljö. console.log("User logged in:", data) - setUser(data) - setModal(null) + setUser(data) + setModal(null) }} /> )} diff --git a/frontend/src/api.js b/frontend/src/api.js index ee6f3531..95882da4 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1 +1,2 @@ +// SÄKERHETSBRIST (Information Disclosure): URL:en till backend är hårdkodad och synlig i frontend-koden. Angriparen kan enkelt identifiera var API:et finns och rikta attacker direkt dit. URL:en bör inte exponeras i koden utan hanteras på ett säkrare sätt. 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..3f99862a 100644 --- a/frontend/src/components/AuthModal.jsx +++ b/frontend/src/components/AuthModal.jsx @@ -29,10 +29,12 @@ export const AuthModal = ({ mode, onClose, onSuccess }) => { const data = await res.json() if (!res.ok) throw new Error(data.message || "Something went wrong") + // SÄKERHETSBRIST (Information Disclosure): console.log("Auth successful:", data) och console.log(err) loggar känslig autentiseringsdata i webbläsarens konsol, synligt för vem som helst med tillgång till dev-verktygen. console.log("Auth successful:", data) onSuccess(data) } catch (err) { console.log(err) + // SÄKERHETSBRIST (User Enumeration): setError(err.message) visar exakta felmeddelanden från backend direkt i gränssnittet vilket kan avslöja för en angripare om ett konto existerar eller inte. setError(err.message) } finally { setSubmitting(false) @@ -41,6 +43,7 @@ export const AuthModal = ({ mode, onClose, onSuccess }) => { return (
+ {/* SÄKERHETSBRIST (Brute Force): Saknar begränsning av antal inloggningsförsök i formuläret, kan skickas hur många gånger som helst utan blockering. */}
e.stopPropagation()} onSubmit={handleSubmit} @@ -78,6 +81,7 @@ export const AuthModal = ({ mode, onClose, onSuccess }) => { /> )} +{/* POSITIVT: Använder type="password" för lösenordsfältet så att texten döljs. autoComplete är korrekt satt för respektive fält.*/} const [errorMessage, setErrorMessage] = useState("") const [submitting, setSubmitting] = useState(false) +// POSITIVT: Kräver att användaren är inloggad innan formuläret visas. Skickar JWT-token med varje anrop via Authorization-headern. Hanterar 401-svar korrekt via onUnauthorized. const handleFormSubmit = async (event) => { event.preventDefault() setSubmitting(true) @@ -20,6 +21,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) => body: JSON.stringify({ message: newPost }), }) + // SÄKERHETSBRIST (Information Disclosure): console.log("Token being sent:", user?.response?.accessToken) loggar JWT-token i klartext i webbläsarens konsol. Angripare med tillgång till konsolen kan stjäla token och utge sig för att vara användaren. console.log("Token being sent:", user?.response?.accessToken) if (res.status === 401) { @@ -56,6 +58,7 @@ export const PostMessage = ({ newMessage, fetchPosts, user, onUnauthorized }) =>

What's making you happy right now?

+ {/* SÄKERHETSBRIST (XSS/Injection): textarea-fältet saknar maxlength och ingen sanering av input sker innan det skickas till backend, skadlig kod kan skickas och lagras i databasen. */}