From 0e8cb615316783e5e402d20bf713cda5ffce5683 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:29:08 +0200 Subject: [PATCH 01/88] Added basic login functionality --- src/app/api/login/route.js | 20 ++++++++++++++++++++ src/app/login/page.jsx | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/app/api/login/route.js diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js new file mode 100644 index 0000000..8b6b1dd --- /dev/null +++ b/src/app/api/login/route.js @@ -0,0 +1,20 @@ +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`${payload.Username} and ${payload.Password} received in the login API`) + console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) + + // Verify if username and password match login info + if (payload.Username === 'admin' && payload.Password === 'password') { + return new Response(JSON.stringify({ message: 'Login successful' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } else { + return new Response(JSON.stringify({ error: 'Invalid credentials' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } +} diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index d5c46c8..69c7dff 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,14 +1,46 @@ +'use client' +import { useState } from 'react' + export default function Login() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + return (

Login

-
+ { + ev.preventDefault() + const response = fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + console.log(`Login form submitted ${Username} and ${Password}`) + }} + >

Username

- + setUsername(ev.target.value)} + />

Password

- + setPassword(ev.target.value)} + />
From 0b778888e25200a0c326797179a3bd34ab7e0d4d Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:59:36 +0200 Subject: [PATCH 02/88] Added username cookie when login is correct --- src/app/api/login/route.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 8b6b1dd..0ea421d 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import { NextResponse } from 'next/server' + export async function POST(request) { const payload = await request.json() @@ -7,14 +9,10 @@ export async function POST(request) { // Verify if username and password match login info if (payload.Username === 'admin' && payload.Password === 'password') { - return new Response(JSON.stringify({ message: 'Login successful' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) + const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) + response.cookies.set('Username', payload.Username, {}) + return response } else { - return new Response(JSON.stringify({ error: 'Invalid credentials' }), { - status: 401, - headers: { 'Content-Type': 'application/json' }, - }) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From 91ed064d641dea3cb67a1abf8d0d432e918a54b5 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:22:28 +0200 Subject: [PATCH 03/88] Added test page for login --- src/app/login/page.jsx | 3 +++ src/app/login/secret/page.jsx | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 69c7dff..de95bef 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,9 +1,11 @@ 'use client' import { useState } from 'react' +import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const router = useRouter() return (
@@ -24,6 +26,7 @@ export default function Login() { Password, }), }) + router.push('/login/secret') console.log(`Login form submitted ${Username} and ${Password}`) }} > diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx new file mode 100644 index 0000000..60ec8c6 --- /dev/null +++ b/src/app/login/secret/page.jsx @@ -0,0 +1,19 @@ +import { cookies } from 'next/headers' + +export default function Secret() { + const allCookies = cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
+

User {username} logged in!

+

Hello {username}!

+
+ ) + } else { + console.log('User is not logged in') + return

You are not logged in!

+ } +} From 55bcc0257608d484baa9888b7900c8f9dd9654a3 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:30:44 +0200 Subject: [PATCH 04/88] Fixed bug navigating to secret page before verifying if login is ok --- src/app/login/page.jsx | 11 +++++++---- src/app/login/secret/page.jsx | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index de95bef..422b2a9 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -14,9 +14,9 @@ export default function Login() {
{ + onSubmit={async (ev) => { ev.preventDefault() - const response = fetch('/api/login', { + const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -26,8 +26,11 @@ export default function Login() { Password, }), }) - router.push('/login/secret') - console.log(`Login form submitted ${Username} and ${Password}`) + if (response.ok) { + router.push('/login/secret') + } else { + alert('Fel användarnamn eller lösenord') + } }} >

Username

diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 60ec8c6..90e5ed1 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,7 +1,7 @@ import { cookies } from 'next/headers' -export default function Secret() { - const allCookies = cookies() +export default async function Secret() { + const allCookies = await cookies() const username = allCookies.get('Username')?.value ?? null if (username) { From c11210105d483302ff58f9c8198b2280d70f3939 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:42:01 +0200 Subject: [PATCH 05/88] Added logout button to clear auth cookie and navigating to login page --- src/app/api/logout/route.js | 9 +++++++++ src/app/login/secret/page.jsx | 2 ++ src/components/LogoutButton.jsx | 13 +++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/app/api/logout/route.js create mode 100644 src/components/LogoutButton.jsx diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js new file mode 100644 index 0000000..314b2d3 --- /dev/null +++ b/src/app/api/logout/route.js @@ -0,0 +1,9 @@ +import { cookies } from 'next/headers' + +export async function POST() { + cookies().set('Username', '') + return new Response(JSON.stringify({ message: 'Logged out' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) +} diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 90e5ed1..76c792d 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,4 +1,5 @@ import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' export default async function Secret() { const allCookies = await cookies() @@ -10,6 +11,7 @@ export default async function Secret() {

User {username} logged in!

Hello {username}!

+
) } else { diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx new file mode 100644 index 0000000..a2e7047 --- /dev/null +++ b/src/components/LogoutButton.jsx @@ -0,0 +1,13 @@ +'use client' +import { useRouter } from 'next/navigation' + +export default function LogoutButton() { + const router = useRouter() + + async function handleLogout() { + await fetch('/api/logout', { method: 'POST' }) + router.push('/login') + } + + return +} From 1902b53e28379ad05bbd8f954be76325908a6f36 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:08 +0200 Subject: [PATCH 06/88] Added user schema for MongoDB model with username and password fields --- src/lib/db/models/User.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/lib/db/models/User.js diff --git a/src/lib/db/models/User.js b/src/lib/db/models/User.js new file mode 100644 index 0000000..63f83e6 --- /dev/null +++ b/src/lib/db/models/User.js @@ -0,0 +1,8 @@ +import mongoose from 'mongoose' + +const userSchema = new mongoose.Schema({ + Username: { type: String, required: true }, + Password: { type: String, required: true }, +}) + +export default mongoose.models.User || mongoose.model('User', userSchema) From f362a922c52d0dfb461f3756a2073163be679b89 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:57 +0200 Subject: [PATCH 07/88] Refactor login API to verify credentials against database --- src/app/api/login/route.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 0ea421d..e3c0e66 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import connectDB from '@/lib/db/connectDB' +import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' export async function POST(request) { @@ -7,12 +9,17 @@ export async function POST(request) { console.log(`${payload.Username} and ${payload.Password} received in the login API`) console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) - // Verify if username and password match login info - if (payload.Username === 'admin' && payload.Password === 'password') { + await connectDB() + let login = await findUserByUsername(payload.Username) + + console.log(`${login.Username} found in the database`) + if (login.Username === payload.Username && login.Password === payload.Password) { + console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) response.cookies.set('Username', payload.Username, {}) return response } else { + console.log(`Login failed for username: ${payload.Username}`) return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From 731a023007e31678a61657f3d722790b4f87a31c Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:07:31 +0200 Subject: [PATCH 08/88] Refactor logout API to await cookie before clearing username --- src/app/api/logout/route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index 314b2d3..a2cbb9f 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -1,7 +1,8 @@ import { cookies } from 'next/headers' export async function POST() { - cookies().set('Username', '') + const storeCookie = await cookies() + storeCookie.set('Username', '') return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, From b268d46895c8872d47e3a75d1b5e9e065ab008da Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:08:12 +0200 Subject: [PATCH 09/88] Added user registration API --- src/app/api/register/route.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/app/api/register/route.js diff --git a/src/app/api/register/route.js b/src/app/api/register/route.js new file mode 100644 index 0000000..46c8500 --- /dev/null +++ b/src/app/api/register/route.js @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server' +import { createUser } from '@/lib/db/userDbService' + +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`Registering user with username: ${payload.Username}`) + + // Create a new user in the database + try { + const newUser = await createUser(payload.Username, payload.Password) + console.log(`User created successfully: ${newUser.Username}`) + return NextResponse.json({ message: 'User registered successfully' }, { status: 201 }) + } catch (error) { + console.error(`Error creating user: ${error.message}`) + return NextResponse.json({ error: 'Failed to register user' }, { status: 500 }) + } +} From 456ded1d906dcbebf1515a7ccf6126c3759e59c3 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:09:50 +0200 Subject: [PATCH 10/88] Update navigation on successful login --- src/app/login/page.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 422b2a9..8d552b7 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const [Error, setError] = useState('') const router = useRouter() return ( @@ -27,12 +28,19 @@ export default function Login() { }), }) if (response.ok) { - router.push('/login/secret') + router.push('/login/account') } else { - alert('Fel användarnamn eller lösenord') + setError(true) } }} > + {Error &&

Fel användarnamn eller lösenord

} +
+

Inget konto?

+ +

Username

Date: Mon, 2 Jun 2025 11:11:08 +0200 Subject: [PATCH 11/88] Added account page to display very basic user login status and provide logout functionality --- src/app/login/account/page.jsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/app/login/account/page.jsx diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx new file mode 100644 index 0000000..fe2b882 --- /dev/null +++ b/src/app/login/account/page.jsx @@ -0,0 +1,27 @@ +import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' + +export default async function Secret() { + const allCookies = await cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
+
+

User {username} logged in!

+

Hello {username}!

+ +
+
+

Button

+ +
+
+ ) + } else { + console.log('User is not logged in') + return

You are not logged in!

+ } +} From 2c8fd19f207f793e7a1f97525a3d38a418c62ae4 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:05 +0200 Subject: [PATCH 12/88] Remove Secret page that is now the account page --- src/app/login/secret/page.jsx | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx deleted file mode 100644 index 76c792d..0000000 --- a/src/app/login/secret/page.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { cookies } from 'next/headers' -import LogoutButton from '@/components/LogoutButton' - -export default async function Secret() { - const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - - if (username) { - console.log(`User ${username} is logged in`) - return ( -
-

User {username} logged in!

-

Hello {username}!

- -
- ) - } else { - console.log('User is not logged in') - return

You are not logged in!

- } -} From da9e82086713a031fa2f25ee5255aa3ce8842cee Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:39 +0200 Subject: [PATCH 13/88] Added registration page to create accounts --- src/app/register/page.jsx | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/app/register/page.jsx diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx new file mode 100644 index 0000000..cc1695b --- /dev/null +++ b/src/app/register/page.jsx @@ -0,0 +1,52 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' + +export default function Register() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + const [Error, setError] = useState(false) + const router = useRouter() + + return ( + { + ev.preventDefault() + const response = await fetch('/api/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + if (response.ok) { + router.push('/login') + } else { + setError(true) + } + }} + > + {Error &&

Användaren finns redan

} +

Registrera konto

+

Username

+ setUsername(ev.target.value)} + /> +

Password

+ setPassword(ev.target.value)} + /> + + + ) +} From 25112165c1890b3bcb2809a0b5345b6dbc0e3f8b Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:15:45 +0200 Subject: [PATCH 14/88] Update NavMenu to manage user login status with cookie checks --- src/components/NavMenu.jsx | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 01ee7e6..5ecc716 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,5 +1,5 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' export default function NavMenu() { @@ -8,8 +8,23 @@ export default function NavMenu() { const [aboutClass, setAboutClass] = useState('header__nav-item') const [adminClass, setAdminClass] = useState('header__nav-item') const [loginClass, setLoginClass] = useState('header__nav-item') + const [isLoggedIn, setIsLoggedIn] = useState(false) const router = useRouter() + // Checks if user is logged in by checking cookies + useEffect(() => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + console.log(isLoggedIn ? 'User is logged in' : 'User is not logged in') + }, []) + + const updateLoginStatus = () => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + } + const handleHomeClick = () => { setHomeClass('header__nav-item menu-active') setMoviesClass('header__nav-item') @@ -91,10 +106,16 @@ export default function NavMenu() {
  • { - handleLoginClick() + if (isLoggedIn) { + router.push('/login/account') + updateLoginStatus() + } else { + handleLoginClick() + updateLoginStatus() + } }} > - LOGIN + {isLoggedIn ? 'KONTO' : 'LOGIN'}
  • From dfc6dc0dfcbd26428b13ad5c74a40c4ea3ed4128 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:16:19 +0200 Subject: [PATCH 15/88] Added basic error text styling to login page --- src/styles/loginPage.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/loginPage.scss b/src/styles/loginPage.scss index cdea79d..10ffe3a 100644 --- a/src/styles/loginPage.scss +++ b/src/styles/loginPage.scss @@ -46,6 +46,10 @@ color: #295377; } + &__error_text { + color: red; + } + &__form_password { color: #295377; } From 4105b447abd74e675fbbad4d11a70a651a97d8b6 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:23:39 +0200 Subject: [PATCH 16/88] Added user management functions including creation, retrieval, deletion, and password update --- src/lib/db/userDbService.js | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/lib/db/userDbService.js diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js new file mode 100644 index 0000000..0837b43 --- /dev/null +++ b/src/lib/db/userDbService.js @@ -0,0 +1,60 @@ +import User from './models/User' +import connectDB from './connectDB' + +// Work in progress: testing. +export async function getAllUsers() { + await connectDB() + return await User.find().select('Username') +} + +// Work in progress: testing. +export async function findUserByUsername(username) { + console.log(`Finding user with username: ${username}`) + await connectDB() + + // Work in progress: testing. + const findUser = await User.findOne({ Username: username }) + console.log(`User found: ${findUser ? findUser.Username : 'No user found'}`) + if (!findUser) { + console.log(`User with username ${username} not found.`) + throw new Error('Användaren kunde inte hittas.') + } + return findUser +} + +// Creates a new user. +// Password is in plain text, work in progress to hash it. +export async function createUser(username, password) { + console.log(`Creating user with username: ${username} and password: ${password}`) + + await connectDB() + + const existingUser = await User.findOne({ Username: username }) + if (username && existingUser) { + console.log(`User with username ${username} already exists.`) + throw new Error('Användarnamnet finns redan, vänligen välj ett annat.') + } + + const newUser = await User.create({ Username: username, Password: password }) + console.log(`User created successfully: ${newUser.Username}`) + return newUser +} + +// Work in progress: testing. +export async function deleteUserByUsername(username) { + await connectDB() + return await User.findOneAndDelete({ Username: username }) +} + +// Work in progress: testing. +export async function updateUserPassword(username, newPassword) { + await connectDB() + return await User.findOneAndUpdate({ Username: username }, { Password: newPassword }, { new: true }) +} + +// Work in progress: testing. +export async function userExists(username) { + await connectDB() + const user = await User.findOne({ Username: username }) + return !!user +} From 8b303a9b14e33a1d07e114277f71cdd05d5043f7 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:29:08 +0200 Subject: [PATCH 17/88] Added basic login functionality --- src/app/api/login/route.js | 20 ++++++++++++++++++++ src/app/login/page.jsx | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/app/api/login/route.js diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js new file mode 100644 index 0000000..8b6b1dd --- /dev/null +++ b/src/app/api/login/route.js @@ -0,0 +1,20 @@ +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`${payload.Username} and ${payload.Password} received in the login API`) + console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) + + // Verify if username and password match login info + if (payload.Username === 'admin' && payload.Password === 'password') { + return new Response(JSON.stringify({ message: 'Login successful' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } else { + return new Response(JSON.stringify({ error: 'Invalid credentials' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } +} diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index d5c46c8..69c7dff 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,14 +1,46 @@ +'use client' +import { useState } from 'react' + export default function Login() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + return (

    Login

    -
    + { + ev.preventDefault() + const response = fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + console.log(`Login form submitted ${Username} and ${Password}`) + }} + >

    Username

    - + setUsername(ev.target.value)} + />

    Password

    - + setPassword(ev.target.value)} + />
    From 57aa3a5e9632d9553603f529adcaec53d3d619ad Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:59:36 +0200 Subject: [PATCH 18/88] Added username cookie when login is correct --- src/app/api/login/route.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 8b6b1dd..0ea421d 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import { NextResponse } from 'next/server' + export async function POST(request) { const payload = await request.json() @@ -7,14 +9,10 @@ export async function POST(request) { // Verify if username and password match login info if (payload.Username === 'admin' && payload.Password === 'password') { - return new Response(JSON.stringify({ message: 'Login successful' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) + const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) + response.cookies.set('Username', payload.Username, {}) + return response } else { - return new Response(JSON.stringify({ error: 'Invalid credentials' }), { - status: 401, - headers: { 'Content-Type': 'application/json' }, - }) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From c047570da3e66549f4c82a760afaa1ad08753200 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:22:28 +0200 Subject: [PATCH 19/88] Added test page for login --- src/app/login/page.jsx | 3 +++ src/app/login/secret/page.jsx | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 69c7dff..de95bef 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,9 +1,11 @@ 'use client' import { useState } from 'react' +import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const router = useRouter() return (
    @@ -24,6 +26,7 @@ export default function Login() { Password, }), }) + router.push('/login/secret') console.log(`Login form submitted ${Username} and ${Password}`) }} > diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx new file mode 100644 index 0000000..60ec8c6 --- /dev/null +++ b/src/app/login/secret/page.jsx @@ -0,0 +1,19 @@ +import { cookies } from 'next/headers' + +export default function Secret() { + const allCookies = cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
    +

    User {username} logged in!

    +

    Hello {username}!

    +
    + ) + } else { + console.log('User is not logged in') + return

    You are not logged in!

    + } +} From 74be4ba5d074daed5403b1c8e1b9e4ca30f09e5e Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:30:44 +0200 Subject: [PATCH 20/88] Fixed bug navigating to secret page before verifying if login is ok --- src/app/login/page.jsx | 11 +++++++---- src/app/login/secret/page.jsx | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index de95bef..422b2a9 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -14,9 +14,9 @@ export default function Login() {
    { + onSubmit={async (ev) => { ev.preventDefault() - const response = fetch('/api/login', { + const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -26,8 +26,11 @@ export default function Login() { Password, }), }) - router.push('/login/secret') - console.log(`Login form submitted ${Username} and ${Password}`) + if (response.ok) { + router.push('/login/secret') + } else { + alert('Fel användarnamn eller lösenord') + } }} >

    Username

    diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 60ec8c6..90e5ed1 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,7 +1,7 @@ import { cookies } from 'next/headers' -export default function Secret() { - const allCookies = cookies() +export default async function Secret() { + const allCookies = await cookies() const username = allCookies.get('Username')?.value ?? null if (username) { From f00010f56c291a2d314d288e89b3b46a379ba68f Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:42:01 +0200 Subject: [PATCH 21/88] Added logout button to clear auth cookie and navigating to login page --- src/app/api/logout/route.js | 9 +++++++++ src/app/login/secret/page.jsx | 2 ++ src/components/LogoutButton.jsx | 13 +++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/app/api/logout/route.js create mode 100644 src/components/LogoutButton.jsx diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js new file mode 100644 index 0000000..314b2d3 --- /dev/null +++ b/src/app/api/logout/route.js @@ -0,0 +1,9 @@ +import { cookies } from 'next/headers' + +export async function POST() { + cookies().set('Username', '') + return new Response(JSON.stringify({ message: 'Logged out' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) +} diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 90e5ed1..76c792d 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,4 +1,5 @@ import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' export default async function Secret() { const allCookies = await cookies() @@ -10,6 +11,7 @@ export default async function Secret() {

    User {username} logged in!

    Hello {username}!

    +
    ) } else { diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx new file mode 100644 index 0000000..a2e7047 --- /dev/null +++ b/src/components/LogoutButton.jsx @@ -0,0 +1,13 @@ +'use client' +import { useRouter } from 'next/navigation' + +export default function LogoutButton() { + const router = useRouter() + + async function handleLogout() { + await fetch('/api/logout', { method: 'POST' }) + router.push('/login') + } + + return +} From f6e61440dc5ce41948f8543a8e85a05cbedf8629 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:08 +0200 Subject: [PATCH 22/88] Added user schema for MongoDB model with username and password fields --- src/lib/db/models/User.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/lib/db/models/User.js diff --git a/src/lib/db/models/User.js b/src/lib/db/models/User.js new file mode 100644 index 0000000..63f83e6 --- /dev/null +++ b/src/lib/db/models/User.js @@ -0,0 +1,8 @@ +import mongoose from 'mongoose' + +const userSchema = new mongoose.Schema({ + Username: { type: String, required: true }, + Password: { type: String, required: true }, +}) + +export default mongoose.models.User || mongoose.model('User', userSchema) From 94300a040a12053b1d4b0a82ee1f98e4bae2c3c5 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:57 +0200 Subject: [PATCH 23/88] Refactor login API to verify credentials against database --- src/app/api/login/route.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 0ea421d..e3c0e66 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import connectDB from '@/lib/db/connectDB' +import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' export async function POST(request) { @@ -7,12 +9,17 @@ export async function POST(request) { console.log(`${payload.Username} and ${payload.Password} received in the login API`) console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) - // Verify if username and password match login info - if (payload.Username === 'admin' && payload.Password === 'password') { + await connectDB() + let login = await findUserByUsername(payload.Username) + + console.log(`${login.Username} found in the database`) + if (login.Username === payload.Username && login.Password === payload.Password) { + console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) response.cookies.set('Username', payload.Username, {}) return response } else { + console.log(`Login failed for username: ${payload.Username}`) return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From 52d751bde564d149b0f2a8d2ec45cdad91a5ee5d Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:07:31 +0200 Subject: [PATCH 24/88] Refactor logout API to await cookie before clearing username --- src/app/api/logout/route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index 314b2d3..a2cbb9f 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -1,7 +1,8 @@ import { cookies } from 'next/headers' export async function POST() { - cookies().set('Username', '') + const storeCookie = await cookies() + storeCookie.set('Username', '') return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, From 12efa7e205a431be20a780f92f542498e70c0b78 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:08:12 +0200 Subject: [PATCH 25/88] Added user registration API --- src/app/api/register/route.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/app/api/register/route.js diff --git a/src/app/api/register/route.js b/src/app/api/register/route.js new file mode 100644 index 0000000..46c8500 --- /dev/null +++ b/src/app/api/register/route.js @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server' +import { createUser } from '@/lib/db/userDbService' + +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`Registering user with username: ${payload.Username}`) + + // Create a new user in the database + try { + const newUser = await createUser(payload.Username, payload.Password) + console.log(`User created successfully: ${newUser.Username}`) + return NextResponse.json({ message: 'User registered successfully' }, { status: 201 }) + } catch (error) { + console.error(`Error creating user: ${error.message}`) + return NextResponse.json({ error: 'Failed to register user' }, { status: 500 }) + } +} From 4cb0450db621bbcb70f9ca4a7657191532adb048 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:09:50 +0200 Subject: [PATCH 26/88] Update navigation on successful login --- src/app/login/page.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 422b2a9..8d552b7 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const [Error, setError] = useState('') const router = useRouter() return ( @@ -27,12 +28,19 @@ export default function Login() { }), }) if (response.ok) { - router.push('/login/secret') + router.push('/login/account') } else { - alert('Fel användarnamn eller lösenord') + setError(true) } }} > + {Error &&

    Fel användarnamn eller lösenord

    } +
    +

    Inget konto?

    + +

    Username

    Date: Mon, 2 Jun 2025 11:11:08 +0200 Subject: [PATCH 27/88] Added account page to display very basic user login status and provide logout functionality --- src/app/login/account/page.jsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/app/login/account/page.jsx diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx new file mode 100644 index 0000000..fe2b882 --- /dev/null +++ b/src/app/login/account/page.jsx @@ -0,0 +1,27 @@ +import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' + +export default async function Secret() { + const allCookies = await cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
    +
    +

    User {username} logged in!

    +

    Hello {username}!

    + +
    +
    +

    Button

    + +
    +
    + ) + } else { + console.log('User is not logged in') + return

    You are not logged in!

    + } +} From a0a30c9513e761d333ef16e7662ce1cfa12af2c9 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:05 +0200 Subject: [PATCH 28/88] Remove Secret page that is now the account page --- src/app/login/secret/page.jsx | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx deleted file mode 100644 index 76c792d..0000000 --- a/src/app/login/secret/page.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { cookies } from 'next/headers' -import LogoutButton from '@/components/LogoutButton' - -export default async function Secret() { - const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - - if (username) { - console.log(`User ${username} is logged in`) - return ( -
    -

    User {username} logged in!

    -

    Hello {username}!

    - -
    - ) - } else { - console.log('User is not logged in') - return

    You are not logged in!

    - } -} From 402f38ffbdf6d32fc761ad502acb30bf04ea0022 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:39 +0200 Subject: [PATCH 29/88] Added registration page to create accounts --- src/app/register/page.jsx | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/app/register/page.jsx diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx new file mode 100644 index 0000000..cc1695b --- /dev/null +++ b/src/app/register/page.jsx @@ -0,0 +1,52 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' + +export default function Register() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + const [Error, setError] = useState(false) + const router = useRouter() + + return ( + { + ev.preventDefault() + const response = await fetch('/api/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + if (response.ok) { + router.push('/login') + } else { + setError(true) + } + }} + > + {Error &&

    Användaren finns redan

    } +

    Registrera konto

    +

    Username

    + setUsername(ev.target.value)} + /> +

    Password

    + setPassword(ev.target.value)} + /> + + + ) +} From 70c0c5cdb8cb0c3101a5648a0e1a0f6880d0531c Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:15:45 +0200 Subject: [PATCH 30/88] Update NavMenu to manage user login status with cookie checks --- src/components/NavMenu.jsx | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 01ee7e6..5ecc716 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,5 +1,5 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' export default function NavMenu() { @@ -8,8 +8,23 @@ export default function NavMenu() { const [aboutClass, setAboutClass] = useState('header__nav-item') const [adminClass, setAdminClass] = useState('header__nav-item') const [loginClass, setLoginClass] = useState('header__nav-item') + const [isLoggedIn, setIsLoggedIn] = useState(false) const router = useRouter() + // Checks if user is logged in by checking cookies + useEffect(() => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + console.log(isLoggedIn ? 'User is logged in' : 'User is not logged in') + }, []) + + const updateLoginStatus = () => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + } + const handleHomeClick = () => { setHomeClass('header__nav-item menu-active') setMoviesClass('header__nav-item') @@ -91,10 +106,16 @@ export default function NavMenu() {
  • { - handleLoginClick() + if (isLoggedIn) { + router.push('/login/account') + updateLoginStatus() + } else { + handleLoginClick() + updateLoginStatus() + } }} > - LOGIN + {isLoggedIn ? 'KONTO' : 'LOGIN'}
  • From e660668e37b25436333dae3d3ad6d9d1d0ad50d1 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:16:19 +0200 Subject: [PATCH 31/88] Added basic error text styling to login page --- src/styles/loginPage.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/loginPage.scss b/src/styles/loginPage.scss index cdea79d..10ffe3a 100644 --- a/src/styles/loginPage.scss +++ b/src/styles/loginPage.scss @@ -46,6 +46,10 @@ color: #295377; } + &__error_text { + color: red; + } + &__form_password { color: #295377; } From 0e17d987f25c6b96b8c595ddb494dee2dd0f7274 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:23:39 +0200 Subject: [PATCH 32/88] Added user management functions including creation, retrieval, deletion, and password update --- src/lib/db/userDbService.js | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/lib/db/userDbService.js diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js new file mode 100644 index 0000000..0837b43 --- /dev/null +++ b/src/lib/db/userDbService.js @@ -0,0 +1,60 @@ +import User from './models/User' +import connectDB from './connectDB' + +// Work in progress: testing. +export async function getAllUsers() { + await connectDB() + return await User.find().select('Username') +} + +// Work in progress: testing. +export async function findUserByUsername(username) { + console.log(`Finding user with username: ${username}`) + await connectDB() + + // Work in progress: testing. + const findUser = await User.findOne({ Username: username }) + console.log(`User found: ${findUser ? findUser.Username : 'No user found'}`) + if (!findUser) { + console.log(`User with username ${username} not found.`) + throw new Error('Användaren kunde inte hittas.') + } + return findUser +} + +// Creates a new user. +// Password is in plain text, work in progress to hash it. +export async function createUser(username, password) { + console.log(`Creating user with username: ${username} and password: ${password}`) + + await connectDB() + + const existingUser = await User.findOne({ Username: username }) + if (username && existingUser) { + console.log(`User with username ${username} already exists.`) + throw new Error('Användarnamnet finns redan, vänligen välj ett annat.') + } + + const newUser = await User.create({ Username: username, Password: password }) + console.log(`User created successfully: ${newUser.Username}`) + return newUser +} + +// Work in progress: testing. +export async function deleteUserByUsername(username) { + await connectDB() + return await User.findOneAndDelete({ Username: username }) +} + +// Work in progress: testing. +export async function updateUserPassword(username, newPassword) { + await connectDB() + return await User.findOneAndUpdate({ Username: username }, { Password: newPassword }, { new: true }) +} + +// Work in progress: testing. +export async function userExists(username) { + await connectDB() + const user = await User.findOne({ Username: username }) + return !!user +} From ed670dd7098edc9344e90e49a1333b5bb5835b32 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:41:04 +0200 Subject: [PATCH 33/88] Added admin user management functionality and update admin panel navigation --- src/app/admin/page.jsx | 28 ++++- src/components/admin/AdminCreateUser.jsx | 130 +++++++++++++++++++++++ src/components/admin/AdminTabNav.jsx | 3 + 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/components/admin/AdminCreateUser.jsx diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index 4a8e9a3..bad22d2 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -1,6 +1,5 @@ 'use client' - -import { useState } from 'react' +import { useState, useEffect } from 'react' import AdminRoomForm from '@/components/admin/AdminRoomForm' import AdminScreeningForm from '@/components/admin/AdminScreeningForm' import AdminMovieForm from '@/components/admin/AdminMovieForm' @@ -11,10 +10,20 @@ import SuccessModal from '@/components/SuccessModal' import { useAdminData } from '@/hooks/useAdminData' import AdminTabNav from '@/components/admin/AdminTabNav' import AdminRoomList from '@/components/admin/AdminRoomList' +import AdminCreateUser from '@/components/admin/AdminCreateUser' export default function AdminPanel() { + const [isAdmin, setIsAdmin] = useState(false) + const [checked, setChecked] = useState(false) const [activeTab, setActiveTab] = useState('list') + useEffect(() => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const adminCookie = cookies.find((c) => c.startsWith('Admin=')) + setIsAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + setChecked(true) + }, []) + const { movies, screenings, @@ -40,6 +49,14 @@ export default function AdminPanel() { confirmDeleteRoom, } = useAdminData() + if (!checked) { + return
    Laddar...
    + } + + if (!isAdmin) { + return

    Du är inte admin!

    + } + return (
    @@ -116,6 +133,13 @@ export default function AdminPanel() {
    )} + {activeTab === 'user1' && ( + <> +
    + +
    + + )}
    {successMessage && setSuccessMessage(null)} />} diff --git a/src/components/admin/AdminCreateUser.jsx b/src/components/admin/AdminCreateUser.jsx new file mode 100644 index 0000000..a36911a --- /dev/null +++ b/src/components/admin/AdminCreateUser.jsx @@ -0,0 +1,130 @@ +'use client' +import { useState } from 'react' + +export default function AdminCreate() { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [success, setSuccess] = useState(null) + const [users, setUsers] = useState([]) // <-- Lägg till denna + const [userError, setUserError] = useState(null) + + async function handleSubmit(e) { + e.preventDefault() + setError(null) + setSuccess(null) + try { + const response = await fetch('/api/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ Username: username, Password: password }), + }) + if (!response.ok) throw new Error('Failed to create user') + setSuccess('Användare skapad!') + setUsername('') + setPassword('') + } catch (err) { + setError('Kunde inte skapa användare') + } + } + + async function fetchUsers() { + setUserError(null) + try { + const response = await fetch('/api/getusers') + if (!response.ok) throw new Error('Kunde inte hämta användare') + const data = await response.json() + setUsers(data) + } catch (err) { + setUserError('Kunde inte hämta användare') + } + } + + async function handleToggleAdmin(username) { + try { + const res = await fetch('/api/toggleadmin', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username }), + }) + if (!res.ok) throw new Error('Kunde inte toggla admin') + // Uppdatera användarlistan efter toggling + fetchUsers() + } catch (err) { + setUserError('Kunde inte toggla admin') + } + } + + async function handleDeleteUser(username) { + try { + const res = await fetch('/api/deleteuser', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username }), + }) + if (!res.ok) throw new Error('Kunde inte ta bort användare') + fetchUsers() + } catch (err) { + setUserError('Kunde inte ta bort användare') + } + } + + return ( +
    +
    +

    Skapa konto

    +
    + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
    + {error &&
    {error}
    } + {success &&
    {success}
    } +
    +
    +
    +

    Användare

    +

    Här kan du hantera befintliga användare.

    + +
    + {userError &&
    {userError}
    } +
    + + {Array.isArray(users) && + users.map((user) => ( + //
    +

    + {user.Username} {user.Admin === true || user.Admin === 'true' ? '(Admin)' : ''} + + +

    + //
    + ))} +
    +
    + ) +} diff --git a/src/components/admin/AdminTabNav.jsx b/src/components/admin/AdminTabNav.jsx index 85382f3..15c56f1 100644 --- a/src/components/admin/AdminTabNav.jsx +++ b/src/components/admin/AdminTabNav.jsx @@ -15,6 +15,9 @@ export default function AdminTabNav({ activeTab, setActiveTab }) { + ) } From d28b48930edabc3226dd247ef91160fdc6900ff4 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:42:04 +0200 Subject: [PATCH 34/88] Updated login API to include admin status in response cookies --- src/app/api/login/route.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index e3c0e66..ca531f6 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -13,10 +13,13 @@ export async function POST(request) { let login = await findUserByUsername(payload.Username) console.log(`${login.Username} found in the database`) + console.log(`Is ${payload.Username} an admin? ${login.Admin}`) + if (login.Username === payload.Username && login.Password === payload.Password) { console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) response.cookies.set('Username', payload.Username, {}) + response.cookies.set('Admin', login.Admin ? 'true' : 'false', {}) return response } else { console.log(`Login failed for username: ${payload.Username}`) From 6dea7bb8f04891018aafcddba4d21e4206950cae Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:42:50 +0200 Subject: [PATCH 35/88] Added admin cookie clearing on logout --- src/app/api/logout/route.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index a2cbb9f..dd77e3a 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -3,6 +3,7 @@ import { cookies } from 'next/headers' export async function POST() { const storeCookie = await cookies() storeCookie.set('Username', '') + storeCookie.set('Admin', '') return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, From 28fb89a7e6b40eddf42054f8a85f2ec630ad570b Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:44:22 +0200 Subject: [PATCH 36/88] Updated user management and admin status in user retrieval and creation functions, and add toggleadmin functionality --- src/lib/db/userDbService.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js index 0837b43..5a9b338 100644 --- a/src/lib/db/userDbService.js +++ b/src/lib/db/userDbService.js @@ -4,7 +4,7 @@ import connectDB from './connectDB' // Work in progress: testing. export async function getAllUsers() { await connectDB() - return await User.find().select('Username') + return await User.find().select('Username Admin') } // Work in progress: testing. @@ -12,8 +12,7 @@ export async function findUserByUsername(username) { console.log(`Finding user with username: ${username}`) await connectDB() - // Work in progress: testing. - const findUser = await User.findOne({ Username: username }) + const findUser = await User.findOne({ Username: username }).select('Username Password Admin') console.log(`User found: ${findUser ? findUser.Username : 'No user found'}`) if (!findUser) { console.log(`User with username ${username} not found.`) @@ -35,7 +34,7 @@ export async function createUser(username, password) { throw new Error('Användarnamnet finns redan, vänligen välj ett annat.') } - const newUser = await User.create({ Username: username, Password: password }) + const newUser = await User.create({ Username: username, Password: password, Admin: false }) console.log(`User created successfully: ${newUser.Username}`) return newUser } @@ -58,3 +57,15 @@ export async function userExists(username) { const user = await User.findOne({ Username: username }) return !!user } + +export async function toggleAdmin(username) { + await connectDB() + const user = await User.findOne({ Username: username }) + if (!user) { + throw new Error(`User with username ${username} not found.`) + } + console.log(`Toggling admin status for user: ${username}. Current admin status: ${user.Admin}`) + user.Admin = !user.Admin + await user.save() + return user +} From fc5528e2bccf52203ef5fa41755a2ee98b1d0fec Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:44:48 +0200 Subject: [PATCH 37/88] Added toggle admin functionality with POST endpoint --- src/app/api/toggleadmin/route.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/app/api/toggleadmin/route.js diff --git a/src/app/api/toggleadmin/route.js b/src/app/api/toggleadmin/route.js new file mode 100644 index 0000000..2182c8e --- /dev/null +++ b/src/app/api/toggleadmin/route.js @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server' +import { toggleAdmin } from '@/lib/db/userDbService' + +export async function POST(request) { + const { username } = await request.json() + try { + const user = await toggleAdmin(username) + return NextResponse.json({ success: true, user }) + } catch (err) { + return NextResponse.json({ success: false, error: err.message }, { status: 500 }) + } +} From 571dbc7d1413d409771323f3ea76575a0ec0d342 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:45:04 +0200 Subject: [PATCH 38/88] Added POST endpoint for user deletion functionality --- src/app/api/deleteuser/route.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/api/deleteuser/route.js diff --git a/src/app/api/deleteuser/route.js b/src/app/api/deleteuser/route.js new file mode 100644 index 0000000..9f93492 --- /dev/null +++ b/src/app/api/deleteuser/route.js @@ -0,0 +1,17 @@ +import { deleteUserByUsername } from '@/lib/db/userDbService' + +export async function POST(request) { + const { username } = await request.json() + + if (!username) { + return new Response('Username is required', { status: 400 }) + } + + try { + await deleteUserByUsername(username) + return new Response(`User ${username} deleted successfully`, { status: 200 }) + } catch (error) { + console.error('Error deleting user:', error) + return new Response('Failed to delete user', { status: 500 }) + } +} From 3b0d13308bd86c3ebbe4490e4a2af7364cc7c008 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:45:39 +0200 Subject: [PATCH 39/88] Added GET endpoint for retrieving users with admin authorization --- src/app/api/getusers/route.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/api/getusers/route.js diff --git a/src/app/api/getusers/route.js b/src/app/api/getusers/route.js new file mode 100644 index 0000000..54eb08f --- /dev/null +++ b/src/app/api/getusers/route.js @@ -0,0 +1,17 @@ +import { NextResponse } from 'next/server' +import connectDB from '@/lib/db/connectDB' +import { getAllUsers } from '@/lib/db/userDbService' + +export async function GET(request) { + if (!request.cookies.get('Admin') || request.cookies.get('Admin').value !== 'true') { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + try { + await connectDB() + const users = await getAllUsers() + return NextResponse.json(users, { status: 200 }) + } catch (error) { + console.error('Error fetching users:', error) + return NextResponse.json({ error: 'Failed to fetch users' }, { status: 500 }) + } +} From d0e638bb4de1c0d6a37bf9cac39ff790422e527a Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:46:14 +0200 Subject: [PATCH 40/88] Added POST endpoint for changing user password --- src/app/api/changepassword/route.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/app/api/changepassword/route.js diff --git a/src/app/api/changepassword/route.js b/src/app/api/changepassword/route.js new file mode 100644 index 0000000..c99b890 --- /dev/null +++ b/src/app/api/changepassword/route.js @@ -0,0 +1,18 @@ +import { updateUserPassword } from '@/lib/db/userDbService' +import { NextResponse } from 'next/server' +export async function POST(request) { + console.log(request.Username) + const payload = await request.json() + + console.log(`Received request to update password for user: ${payload.Username}`) + + try { + const updatedUser = await updateUserPassword(payload.Username, payload.Password) + + console.log(`Password updated successfully for user: ${updatedUser.Username}`) + return NextResponse.json({ message: 'Password updated successfully' }, { status: 200 }) + } catch (error) { + console.error(`Error updating password for user ${username}:`, error) + return NextResponse.json({ error: 'Failed to update password' }, { status: 500 }) + } +} From cad7a6f4b1bc64c3c8cb1c86a0a030076b4cd9f0 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:47:05 +0200 Subject: [PATCH 41/88] Added change password page --- src/app/forgotpassword/page.jsx | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/app/forgotpassword/page.jsx diff --git a/src/app/forgotpassword/page.jsx b/src/app/forgotpassword/page.jsx new file mode 100644 index 0000000..696a7d9 --- /dev/null +++ b/src/app/forgotpassword/page.jsx @@ -0,0 +1,57 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' + +export default function Register() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + const [Error, setError] = useState(false) + const router = useRouter() + + return ( +
    { + ev.preventDefault() + const response = await fetch('/api/changepassword', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + if (response.ok) { + router.push('/login') + } else { + setError(true) + } + }} + > +

    Glömt lösenord

    +

    Användarnamn

    + setUsername(ev.target.value)} + /> +

    Nytt lösenord

    + setPassword(ev.target.value)} + /> + +
    + ) +} From cbd8b6a53b7eb42961e979fa68c79f9827555469 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:49:52 +0200 Subject: [PATCH 42/88] Updated login page,register page and account page translating some words to swedish from english to fit the website --- src/app/login/account/page.jsx | 16 ++++++++-------- src/app/login/page.jsx | 14 +++++++++++--- src/app/register/page.jsx | 10 ++++++++-- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx index fe2b882..e59f845 100644 --- a/src/app/login/account/page.jsx +++ b/src/app/login/account/page.jsx @@ -6,22 +6,22 @@ export default async function Secret() { const username = allCookies.get('Username')?.value ?? null if (username) { - console.log(`User ${username} is logged in`) return (
    -

    User {username} logged in!

    -

    Hello {username}!

    +

    Användare {username} har loggat in!

    +

    Hej {username}!

    -
    -

    Button

    - -
    ) } else { console.log('User is not logged in') - return

    You are not logged in!

    + return ( +
    +

    Du är inte inloggad!

    + Logga in +
    + ) } } diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 8d552b7..7957c12 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -11,7 +11,7 @@ export default function Login() { return (
    -

    Login

    +

    Logga in

    Registrera här
    -

    Username

    +

    Användarnamn

    setUsername(ev.target.value)} /> -

    Password

    +

    Lösenord

    setPassword(ev.target.value)} /> + Glömt lösenordet? diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx index cc1695b..04ba256 100644 --- a/src/app/register/page.jsx +++ b/src/app/register/page.jsx @@ -32,17 +32,23 @@ export default function Register() { > {Error &&

    Användaren finns redan

    }

    Registrera konto

    -

    Username

    +

    Användarnamn

    setUsername(ev.target.value)} /> -

    Password

    +

    Lösenord

    setPassword(ev.target.value)} /> From f1bce9d2723238c09a2c3b5ccead13a123d4d60f Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:50:11 +0200 Subject: [PATCH 43/88] Added Admin field to user schema with default value set to false --- src/lib/db/models/User.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/db/models/User.js b/src/lib/db/models/User.js index 63f83e6..78bf249 100644 --- a/src/lib/db/models/User.js +++ b/src/lib/db/models/User.js @@ -3,6 +3,7 @@ import mongoose from 'mongoose' const userSchema = new mongoose.Schema({ Username: { type: String, required: true }, Password: { type: String, required: true }, + Admin: { type: Boolean, default: false }, }) export default mongoose.models.User || mongoose.model('User', userSchema) From 980ec7828195406c0f74e3d62493e544b7f8c188 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:51:18 +0200 Subject: [PATCH 44/88] Updated LogoutButton and NavMenu to improve login status handling and UI updates --- src/components/LogoutButton.jsx | 7 ++++- src/components/NavMenu.jsx | 45 +++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx index a2e7047..78f2520 100644 --- a/src/components/LogoutButton.jsx +++ b/src/components/LogoutButton.jsx @@ -6,8 +6,13 @@ export default function LogoutButton() { async function handleLogout() { await fetch('/api/logout', { method: 'POST' }) + window.dispatchEvent(new Event('loginStatusChanged')) router.push('/login') } - return + return ( + + ) } diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 5ecc716..77281f4 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,6 +1,6 @@ 'use client' import { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' +import { useRouter, usePathname } from 'next/navigation' export default function NavMenu() { const [homeClass, setHomeClass] = useState('header__nav-item') @@ -9,22 +9,24 @@ export default function NavMenu() { const [adminClass, setAdminClass] = useState('header__nav-item') const [loginClass, setLoginClass] = useState('header__nav-item') const [isLoggedIn, setIsLoggedIn] = useState(false) + const [Admin, setAdmin] = useState(false) const router = useRouter() + const pathname = usePathname() // Checks if user is logged in by checking cookies useEffect(() => { - const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - console.log(isLoggedIn ? 'User is logged in' : 'User is not logged in') + const checkLogin = () => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + const adminCookie = cookies.find((c) => c.startsWith('Admin=')) + setAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + } + checkLogin() + window.addEventListener('loginStatusChanged', checkLogin) + return () => window.removeEventListener('loginStatusChanged', checkLogin) }, []) - const updateLoginStatus = () => { - const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - } - const handleHomeClick = () => { setHomeClass('header__nav-item menu-active') setMoviesClass('header__nav-item') @@ -93,7 +95,7 @@ export default function NavMenu() { > OM OSS - {process.env.NODE_ENV === 'development' && ( + {(process.env.NODE_ENV === 'development' || Admin === true) && (
  • { @@ -104,20 +106,31 @@ export default function NavMenu() {
  • )}
  • { if (isLoggedIn) { + setHomeClass('header__nav-item') + setMoviesClass('header__nav-item') + setAboutClass('header__nav-item') + setAdminClass('header__nav-item') + setLoginClass('header__nav-item menu-active') router.push('/login/account') - updateLoginStatus() } else { handleLoginClick() - updateLoginStatus() } }} > - {isLoggedIn ? 'KONTO' : 'LOGIN'} + {isLoggedIn ? 'KONTO' : 'LOGGA IN'}
  • + {process.env.NODE_ENV === 'development' && + console.log(`isLoggedIn:${isLoggedIn} NODE_ENV:${process.env.NODE_ENV} Admin:${Admin}`)} ) } From 4c38bc30db94fca1a425c2898d4adb4d1d199aa7 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 12:31:58 +0200 Subject: [PATCH 45/88] Adding jsonwebtoken --- package-lock.json | 313 ++++++++++++++++++++++++++++++---------------- package.json | 1 + 2 files changed, 207 insertions(+), 107 deletions(-) diff --git a/package-lock.json b/package-lock.json index b192bf3..370c333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", @@ -1171,6 +1172,111 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz", + "integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz", + "integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz", + "integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.1.tgz", + "integrity": "sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.1.tgz", + "integrity": "sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz", + "integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz", + "integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -2184,6 +2290,12 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "dev": true, @@ -2766,6 +2878,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.155", "dev": true, @@ -5307,6 +5428,28 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "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", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -5321,6 +5464,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.6.3", "license": "Apache-2.0", @@ -5587,11 +5751,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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.merge": { "version": "4.6.2", "dev": true, "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==", + "license": "MIT" + }, "node_modules/log-update": { "version": "6.1.0", "dev": true, @@ -6879,7 +7085,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "dev": true, "funding": [ { "type": "github", @@ -6952,7 +7157,6 @@ }, "node_modules/semver": { "version": "7.7.2", - "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8148,111 +8352,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz", - "integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz", - "integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz", - "integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.1.tgz", - "integrity": "sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.1.tgz", - "integrity": "sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz", - "integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz", - "integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package.json b/package.json index c64ee64..6b082b3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", From 1ff5740aced049e131283ec01b92803028447052 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 12:55:27 +0200 Subject: [PATCH 46/88] Adding jwt-decode for jsonwebtoken handling --- package-lock.json | 10 ++++++++++ package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index 370c333..f85d8d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", @@ -5485,6 +5486,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/kareem": { "version": "2.6.3", "license": "Apache-2.0", diff --git a/package.json b/package.json index 6b082b3..1804c63 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", From cc6691b62a0c0e21e1d28fffec1c839c9f1f2322 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 13:59:15 +0200 Subject: [PATCH 47/88] Updated login/logout, account and admin page to work with JWT --- src/app/admin/page.jsx | 19 +++++++++++++++++-- src/app/api/getusers/route.js | 17 +++++++++++++++-- src/app/api/login/route.js | 8 ++++++-- src/app/api/logout/route.js | 3 +-- src/app/login/account/page.jsx | 23 ++++++++++++++++++----- src/components/NavMenu.jsx | 23 +++++++++++++++++++---- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index bad22d2..daa0963 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -11,6 +11,7 @@ import { useAdminData } from '@/hooks/useAdminData' import AdminTabNav from '@/components/admin/AdminTabNav' import AdminRoomList from '@/components/admin/AdminRoomList' import AdminCreateUser from '@/components/admin/AdminCreateUser' +import { jwtDecode } from 'jwt-decode' export default function AdminPanel() { const [isAdmin, setIsAdmin] = useState(false) @@ -19,8 +20,22 @@ export default function AdminPanel() { useEffect(() => { const cookies = document.cookie.split(';').map((c) => c.trim()) - const adminCookie = cookies.find((c) => c.startsWith('Admin=')) - setIsAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + if (jwtCookie) { + console.log('JWT cookie found:', jwtCookie) + const token = jwtCookie.split('=')[1] + try { + console.log('JWT token found:', token) + const decoded = jwtDecode(token) + setIsAdmin(decoded.admin) + } catch (e) { + console.error('Invalid JWT token:', e) + setIsAdmin(false) + } + } else { + console.log('JWT cookie not found') + setIsAdmin(false) + } setChecked(true) }, []) diff --git a/src/app/api/getusers/route.js b/src/app/api/getusers/route.js index 54eb08f..5bc925f 100644 --- a/src/app/api/getusers/route.js +++ b/src/app/api/getusers/route.js @@ -1,11 +1,24 @@ import { NextResponse } from 'next/server' import connectDB from '@/lib/db/connectDB' import { getAllUsers } from '@/lib/db/userDbService' - +import { jwtDecode } from 'jwt-decode' export async function GET(request) { - if (!request.cookies.get('Admin') || request.cookies.get('Admin').value !== 'true') { + const cookieHeader = request.headers.get('cookie') || '' + const cookies = cookieHeader.split(';').map((c) => c.trim()) + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + if (!jwtCookie) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } + + const token = jwtCookie.split('=')[1] + try { + const decoded = jwtDecode(token) + if (!decoded.admin) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + } catch (error) { + return NextResponse.json({ error: 'Invalid token' }, { status: 401 }) + } try { await connectDB() const users = await getAllUsers() diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index ca531f6..8fa6a1a 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,6 +1,7 @@ import connectDB from '@/lib/db/connectDB' import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' +import jsonwebtoken from 'jsonwebtoken' export async function POST(request) { const payload = await request.json() @@ -18,8 +19,11 @@ export async function POST(request) { if (login.Username === payload.Username && login.Password === payload.Password) { console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) - response.cookies.set('Username', payload.Username, {}) - response.cookies.set('Admin', login.Admin ? 'true' : 'false', {}) + + const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { + expiresIn: '1h', + }) + response.cookies.set('JWT', jwtToken, { httpOnly: false }) return response } else { console.log(`Login failed for username: ${payload.Username}`) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index dd77e3a..c28acb1 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -2,8 +2,7 @@ import { cookies } from 'next/headers' export async function POST() { const storeCookie = await cookies() - storeCookie.set('Username', '') - storeCookie.set('Admin', '') + storeCookie.delete('JWT', { httpOnly: false }) return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx index e59f845..990c2e1 100644 --- a/src/app/login/account/page.jsx +++ b/src/app/login/account/page.jsx @@ -1,16 +1,29 @@ import { cookies } from 'next/headers' import LogoutButton from '@/components/LogoutButton' +import { jwtDecode } from 'jwt-decode' export default async function Secret() { const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - + const jwtCookie = allCookies.get('JWT') + let username = null + if (jwtCookie) { + try { + const decoded = jwtDecode(jwtCookie.value) + username = decoded.username + } catch (e) { + console.error('Invalid JWT token:', e) + } + } if (username) { return (
    -
    -

    Användare {username} har loggat in!

    -

    Hej {username}!

    +
    +
    +

    Användare {username} har loggat in!

    +
    +
    +

    Hej {username}!

    +
    diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 77281f4..83f3600 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' import { useRouter, usePathname } from 'next/navigation' +import { jwtDecode } from 'jwt-decode' export default function NavMenu() { const [homeClass, setHomeClass] = useState('header__nav-item') @@ -17,10 +18,24 @@ export default function NavMenu() { useEffect(() => { const checkLogin = () => { const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - const adminCookie = cookies.find((c) => c.startsWith('Admin=')) - setAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + console.log('Checking login status...') + console.log('Cookies:', cookies) + console.log('JWT Cookie:', jwtCookie) + if (jwtCookie) { + const token = jwtCookie.split('=')[1] + try { + const decoded = jwtDecode(token) + setIsLoggedIn(!!decoded.username) + setAdmin(decoded.admin) + } catch (e) { + setIsLoggedIn(false) + setAdmin(false) + } + } else { + setIsLoggedIn(false) + setAdmin(false) + } } checkLogin() window.addEventListener('loginStatusChanged', checkLogin) From 265c069d3c111d3a1cdfd91f4228e54709b04502 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:11:08 +0200 Subject: [PATCH 48/88] Adding bcrypt for password hashing --- package-lock.json | 35 +++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 36 insertions(+) diff --git a/package-lock.json b/package-lock.json index f85d8d7..01021b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "kino-project", "version": "0.1.0", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", @@ -2225,6 +2226,29 @@ "dev": true, "license": "MIT" }, + "node_modules/bcrypt": { + "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": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -6280,6 +6304,17 @@ "license": "MIT", "optional": true }, + "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", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "dev": true, diff --git a/package.json b/package.json index 1804c63..4971318 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "watch-css": "sass --watch src/styles/main.scss:public/dist/styles.css" }, "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", From 08cd091a8c2727e8a43be907107c4fc18dfdf5f7 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:15:30 +0200 Subject: [PATCH 49/88] Added password hashing for user registration and login & updated forgot my password logic --- src/app/api/changepassword/route.js | 2 +- src/app/api/login/route.js | 31 +++++++++++++++++------------ src/app/api/register/route.js | 8 ++++++-- src/lib/db/userDbService.js | 9 +++++---- src/lib/utils/hashPassword.js | 7 +++++++ 5 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 src/lib/utils/hashPassword.js diff --git a/src/app/api/changepassword/route.js b/src/app/api/changepassword/route.js index c99b890..7d3f96c 100644 --- a/src/app/api/changepassword/route.js +++ b/src/app/api/changepassword/route.js @@ -1,7 +1,7 @@ import { updateUserPassword } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' + export async function POST(request) { - console.log(request.Username) const payload = await request.json() console.log(`Received request to update password for user: ${payload.Username}`) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 8fa6a1a..a1f8fd2 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -2,6 +2,8 @@ import connectDB from '@/lib/db/connectDB' import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' import jsonwebtoken from 'jsonwebtoken' +import hashPassword from '@/lib/utils/hashPassword' +import bcrypt from 'bcrypt' export async function POST(request) { const payload = await request.json() @@ -13,20 +15,23 @@ export async function POST(request) { await connectDB() let login = await findUserByUsername(payload.Username) - console.log(`${login.Username} found in the database`) - console.log(`Is ${payload.Username} an admin? ${login.Admin}`) + const secretpassword = await hashPassword(payload.Password) - if (login.Username === payload.Username && login.Password === payload.Password) { + if (login.Username == payload.Username) { console.log(`User ${payload.Username} found in the database`) - const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) - - const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { - expiresIn: '1h', - }) - response.cookies.set('JWT', jwtToken, { httpOnly: false }) - return response - } else { - console.log(`Login failed for username: ${payload.Username}`) - return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) + const isMatch = await bcrypt.compare(payload.Password, login.Password) + console.log(`Password match for user ${payload.Username}: ${isMatch}`) + if (isMatch) { + const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) + const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { + expiresIn: '1h', + }) + response.cookies.set('JWT', jwtToken, { httpOnly: false }) + return response + } else login.Password !== secretpassword + { + console.log(`Password incorrect for user: ${payload.Username}`) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) + } } } diff --git a/src/app/api/register/route.js b/src/app/api/register/route.js index 46c8500..0b69006 100644 --- a/src/app/api/register/route.js +++ b/src/app/api/register/route.js @@ -1,15 +1,19 @@ import { NextResponse } from 'next/server' import { createUser } from '@/lib/db/userDbService' +import bcrypt from 'bcrypt' export async function POST(request) { const payload = await request.json() - // Log the payload for debugging + // Hash the password before storing it console.log(`Registering user with username: ${payload.Username}`) + const salt = await bcrypt.genSalt(15) + const hash = await bcrypt.hash(payload.Password, salt) + const hashedPassword = hash // Create a new user in the database try { - const newUser = await createUser(payload.Username, payload.Password) + const newUser = await createUser(payload.Username, hashedPassword) console.log(`User created successfully: ${newUser.Username}`) return NextResponse.json({ message: 'User registered successfully' }, { status: 201 }) } catch (error) { diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js index 5a9b338..e89c64a 100644 --- a/src/lib/db/userDbService.js +++ b/src/lib/db/userDbService.js @@ -1,15 +1,16 @@ import User from './models/User' import connectDB from './connectDB' +import hashPassword from '@/lib/utils/hashPassword' // Work in progress: testing. export async function getAllUsers() { await connectDB() + console.log('Fetching all users from the database...') return await User.find().select('Username Admin') } // Work in progress: testing. export async function findUserByUsername(username) { - console.log(`Finding user with username: ${username}`) await connectDB() const findUser = await User.findOne({ Username: username }).select('Username Password Admin') @@ -24,8 +25,7 @@ export async function findUserByUsername(username) { // Creates a new user. // Password is in plain text, work in progress to hash it. export async function createUser(username, password) { - console.log(`Creating user with username: ${username} and password: ${password}`) - + hashPassword(password) await connectDB() const existingUser = await User.findOne({ Username: username }) @@ -35,19 +35,20 @@ export async function createUser(username, password) { } const newUser = await User.create({ Username: username, Password: password, Admin: false }) - console.log(`User created successfully: ${newUser.Username}`) return newUser } // Work in progress: testing. export async function deleteUserByUsername(username) { await connectDB() + console.log(`Attempting to delete user with username: ${username}`) return await User.findOneAndDelete({ Username: username }) } // Work in progress: testing. export async function updateUserPassword(username, newPassword) { await connectDB() + newPassword = await hashPassword(newPassword) return await User.findOneAndUpdate({ Username: username }, { Password: newPassword }, { new: true }) } diff --git a/src/lib/utils/hashPassword.js b/src/lib/utils/hashPassword.js new file mode 100644 index 0000000..a80c4fa --- /dev/null +++ b/src/lib/utils/hashPassword.js @@ -0,0 +1,7 @@ +import bcrypt from 'bcrypt' + +export default async function hashPassword(password) { + const salt = await bcrypt.genSalt(15) + const hashedPassword = await bcrypt.hash(password, salt) + return hashedPassword +} From 8210e1571767757a65eb7dc3b3eca0a3d1871c59 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:22:28 +0200 Subject: [PATCH 50/88] Removed some testing logs --- src/app/admin/page.jsx | 4 ---- src/components/NavMenu.jsx | 3 --- src/components/admin/AdminCreateUser.jsx | 2 -- 3 files changed, 9 deletions(-) diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index daa0963..f1b8d4d 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -22,18 +22,14 @@ export default function AdminPanel() { const cookies = document.cookie.split(';').map((c) => c.trim()) const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) if (jwtCookie) { - console.log('JWT cookie found:', jwtCookie) const token = jwtCookie.split('=')[1] try { - console.log('JWT token found:', token) const decoded = jwtDecode(token) setIsAdmin(decoded.admin) } catch (e) { - console.error('Invalid JWT token:', e) setIsAdmin(false) } } else { - console.log('JWT cookie not found') setIsAdmin(false) } setChecked(true) diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 83f3600..8ec424d 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -19,9 +19,6 @@ export default function NavMenu() { const checkLogin = () => { const cookies = document.cookie.split(';').map((c) => c.trim()) const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) - console.log('Checking login status...') - console.log('Cookies:', cookies) - console.log('JWT Cookie:', jwtCookie) if (jwtCookie) { const token = jwtCookie.split('=')[1] try { diff --git a/src/components/admin/AdminCreateUser.jsx b/src/components/admin/AdminCreateUser.jsx index a36911a..ab75ed4 100644 --- a/src/components/admin/AdminCreateUser.jsx +++ b/src/components/admin/AdminCreateUser.jsx @@ -112,7 +112,6 @@ export default function AdminCreate() { {Array.isArray(users) && users.map((user) => ( - //

    {user.Username} {user.Admin === true || user.Admin === 'true' ? '(Admin)' : ''}

    - //
    ))}
    From 3633f27f5d7d98439629ec7d494cbbedb8645195 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:29:08 +0200 Subject: [PATCH 51/88] Added basic login functionality --- src/app/api/login/route.js | 20 ++++++++++++++++++++ src/app/login/page.jsx | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/app/api/login/route.js diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js new file mode 100644 index 0000000..8b6b1dd --- /dev/null +++ b/src/app/api/login/route.js @@ -0,0 +1,20 @@ +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`${payload.Username} and ${payload.Password} received in the login API`) + console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) + + // Verify if username and password match login info + if (payload.Username === 'admin' && payload.Password === 'password') { + return new Response(JSON.stringify({ message: 'Login successful' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } else { + return new Response(JSON.stringify({ error: 'Invalid credentials' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } +} diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index d5c46c8..69c7dff 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,14 +1,46 @@ +'use client' +import { useState } from 'react' + export default function Login() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + return (

    Login

    -
    + { + ev.preventDefault() + const response = fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + console.log(`Login form submitted ${Username} and ${Password}`) + }} + >

    Username

    - + setUsername(ev.target.value)} + />

    Password

    - + setPassword(ev.target.value)} + />
    From 6ed12af5ef5a3a71032102311e54a7d9876a5b29 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 11:59:36 +0200 Subject: [PATCH 52/88] Added username cookie when login is correct --- src/app/api/login/route.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 8b6b1dd..0ea421d 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import { NextResponse } from 'next/server' + export async function POST(request) { const payload = await request.json() @@ -7,14 +9,10 @@ export async function POST(request) { // Verify if username and password match login info if (payload.Username === 'admin' && payload.Password === 'password') { - return new Response(JSON.stringify({ message: 'Login successful' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) + const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) + response.cookies.set('Username', payload.Username, {}) + return response } else { - return new Response(JSON.stringify({ error: 'Invalid credentials' }), { - status: 401, - headers: { 'Content-Type': 'application/json' }, - }) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From efd7e2f53243380e086b37d166bdad9a84aed1ea Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:22:28 +0200 Subject: [PATCH 53/88] Added test page for login --- src/app/login/page.jsx | 3 +++ src/app/login/secret/page.jsx | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 69c7dff..de95bef 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,9 +1,11 @@ 'use client' import { useState } from 'react' +import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const router = useRouter() return (
    @@ -24,6 +26,7 @@ export default function Login() { Password, }), }) + router.push('/login/secret') console.log(`Login form submitted ${Username} and ${Password}`) }} > diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx new file mode 100644 index 0000000..60ec8c6 --- /dev/null +++ b/src/app/login/secret/page.jsx @@ -0,0 +1,19 @@ +import { cookies } from 'next/headers' + +export default function Secret() { + const allCookies = cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
    +

    User {username} logged in!

    +

    Hello {username}!

    +
    + ) + } else { + console.log('User is not logged in') + return

    You are not logged in!

    + } +} From b101ca843ba32b9bf92d37b2d9d6e3a5b40fa94b Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:30:44 +0200 Subject: [PATCH 54/88] Fixed bug navigating to secret page before verifying if login is ok --- src/app/login/page.jsx | 11 +++++++---- src/app/login/secret/page.jsx | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index de95bef..422b2a9 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -14,9 +14,9 @@ export default function Login() {
    { + onSubmit={async (ev) => { ev.preventDefault() - const response = fetch('/api/login', { + const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -26,8 +26,11 @@ export default function Login() { Password, }), }) - router.push('/login/secret') - console.log(`Login form submitted ${Username} and ${Password}`) + if (response.ok) { + router.push('/login/secret') + } else { + alert('Fel användarnamn eller lösenord') + } }} >

    Username

    diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 60ec8c6..90e5ed1 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,7 +1,7 @@ import { cookies } from 'next/headers' -export default function Secret() { - const allCookies = cookies() +export default async function Secret() { + const allCookies = await cookies() const username = allCookies.get('Username')?.value ?? null if (username) { From 0e3670cbb34a9e56178fb17d0d28f61c0e950721 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:42:01 +0200 Subject: [PATCH 55/88] Added logout button to clear auth cookie and navigating to login page --- src/app/api/logout/route.js | 9 +++++++++ src/app/login/secret/page.jsx | 2 ++ src/components/LogoutButton.jsx | 13 +++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/app/api/logout/route.js create mode 100644 src/components/LogoutButton.jsx diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js new file mode 100644 index 0000000..314b2d3 --- /dev/null +++ b/src/app/api/logout/route.js @@ -0,0 +1,9 @@ +import { cookies } from 'next/headers' + +export async function POST() { + cookies().set('Username', '') + return new Response(JSON.stringify({ message: 'Logged out' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) +} diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 90e5ed1..76c792d 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,4 +1,5 @@ import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' export default async function Secret() { const allCookies = await cookies() @@ -10,6 +11,7 @@ export default async function Secret() {

    User {username} logged in!

    Hello {username}!

    +
    ) } else { diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx new file mode 100644 index 0000000..a2e7047 --- /dev/null +++ b/src/components/LogoutButton.jsx @@ -0,0 +1,13 @@ +'use client' +import { useRouter } from 'next/navigation' + +export default function LogoutButton() { + const router = useRouter() + + async function handleLogout() { + await fetch('/api/logout', { method: 'POST' }) + router.push('/login') + } + + return +} From 1903fdce092063b36c08b4d9a014fed3ff433efd Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:08 +0200 Subject: [PATCH 56/88] Added user schema for MongoDB model with username and password fields --- src/lib/db/models/User.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/lib/db/models/User.js diff --git a/src/lib/db/models/User.js b/src/lib/db/models/User.js new file mode 100644 index 0000000..63f83e6 --- /dev/null +++ b/src/lib/db/models/User.js @@ -0,0 +1,8 @@ +import mongoose from 'mongoose' + +const userSchema = new mongoose.Schema({ + Username: { type: String, required: true }, + Password: { type: String, required: true }, +}) + +export default mongoose.models.User || mongoose.model('User', userSchema) From b6854f3e63604d79c8485efe16609460341482af Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:06:57 +0200 Subject: [PATCH 57/88] Refactor login API to verify credentials against database --- src/app/api/login/route.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 0ea421d..e3c0e66 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,3 +1,5 @@ +import connectDB from '@/lib/db/connectDB' +import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' export async function POST(request) { @@ -7,12 +9,17 @@ export async function POST(request) { console.log(`${payload.Username} and ${payload.Password} received in the login API`) console.log(`Login attempt with username: ${payload.Username} and password: ${payload.Password}`) - // Verify if username and password match login info - if (payload.Username === 'admin' && payload.Password === 'password') { + await connectDB() + let login = await findUserByUsername(payload.Username) + + console.log(`${login.Username} found in the database`) + if (login.Username === payload.Username && login.Password === payload.Password) { + console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) response.cookies.set('Username', payload.Username, {}) return response } else { + console.log(`Login failed for username: ${payload.Username}`) return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) } } From 26b0f50c06d8d842da952ab7615b3d6d67c9eb9b Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:07:31 +0200 Subject: [PATCH 58/88] Refactor logout API to await cookie before clearing username --- src/app/api/logout/route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index 314b2d3..a2cbb9f 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -1,7 +1,8 @@ import { cookies } from 'next/headers' export async function POST() { - cookies().set('Username', '') + const storeCookie = await cookies() + storeCookie.set('Username', '') return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, From 3a923454f02c21a8e1111c02db71995a6571573c Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:08:12 +0200 Subject: [PATCH 59/88] Added user registration API --- src/app/api/register/route.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/app/api/register/route.js diff --git a/src/app/api/register/route.js b/src/app/api/register/route.js new file mode 100644 index 0000000..46c8500 --- /dev/null +++ b/src/app/api/register/route.js @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server' +import { createUser } from '@/lib/db/userDbService' + +export async function POST(request) { + const payload = await request.json() + + // Log the payload for debugging + console.log(`Registering user with username: ${payload.Username}`) + + // Create a new user in the database + try { + const newUser = await createUser(payload.Username, payload.Password) + console.log(`User created successfully: ${newUser.Username}`) + return NextResponse.json({ message: 'User registered successfully' }, { status: 201 }) + } catch (error) { + console.error(`Error creating user: ${error.message}`) + return NextResponse.json({ error: 'Failed to register user' }, { status: 500 }) + } +} From 3d4f72c0651599e677883dc3707d01f83f7f7ef8 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:09:50 +0200 Subject: [PATCH 60/88] Update navigation on successful login --- src/app/login/page.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 422b2a9..8d552b7 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation' export default function Login() { const [Username, setUsername] = useState('') const [Password, setPassword] = useState('') + const [Error, setError] = useState('') const router = useRouter() return ( @@ -27,12 +28,19 @@ export default function Login() { }), }) if (response.ok) { - router.push('/login/secret') + router.push('/login/account') } else { - alert('Fel användarnamn eller lösenord') + setError(true) } }} > + {Error &&

    Fel användarnamn eller lösenord

    } +
    +

    Inget konto?

    + +

    Username

    Date: Mon, 2 Jun 2025 11:11:08 +0200 Subject: [PATCH 61/88] Added account page to display very basic user login status and provide logout functionality --- src/app/login/account/page.jsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/app/login/account/page.jsx diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx new file mode 100644 index 0000000..fe2b882 --- /dev/null +++ b/src/app/login/account/page.jsx @@ -0,0 +1,27 @@ +import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' + +export default async function Secret() { + const allCookies = await cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
    +
    +

    User {username} logged in!

    +

    Hello {username}!

    + +
    +
    +

    Button

    + +
    +
    + ) + } else { + console.log('User is not logged in') + return

    You are not logged in!

    + } +} From c64f57011b8808ac4d8c84a4739c8110c2fd89c1 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:05 +0200 Subject: [PATCH 62/88] Remove Secret page that is now the account page --- src/app/login/secret/page.jsx | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx deleted file mode 100644 index 76c792d..0000000 --- a/src/app/login/secret/page.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { cookies } from 'next/headers' -import LogoutButton from '@/components/LogoutButton' - -export default async function Secret() { - const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - - if (username) { - console.log(`User ${username} is logged in`) - return ( -
    -

    User {username} logged in!

    -

    Hello {username}!

    - -
    - ) - } else { - console.log('User is not logged in') - return

    You are not logged in!

    - } -} From 8c23b03fe7c5b1b7f116a404ff3dcc34cf3d5606 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:39 +0200 Subject: [PATCH 63/88] Added registration page to create accounts --- src/app/register/page.jsx | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/app/register/page.jsx diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx new file mode 100644 index 0000000..cc1695b --- /dev/null +++ b/src/app/register/page.jsx @@ -0,0 +1,52 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' + +export default function Register() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + const [Error, setError] = useState(false) + const router = useRouter() + + return ( + { + ev.preventDefault() + const response = await fetch('/api/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + if (response.ok) { + router.push('/login') + } else { + setError(true) + } + }} + > + {Error &&

    Användaren finns redan

    } +

    Registrera konto

    +

    Username

    + setUsername(ev.target.value)} + /> +

    Password

    + setPassword(ev.target.value)} + /> + + + ) +} From a3553a9c63c8bef9eb0b82897cd6e79271f49752 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:15:45 +0200 Subject: [PATCH 64/88] Update NavMenu to manage user login status with cookie checks --- src/components/NavMenu.jsx | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 01ee7e6..5ecc716 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,5 +1,5 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' export default function NavMenu() { @@ -8,8 +8,23 @@ export default function NavMenu() { const [aboutClass, setAboutClass] = useState('header__nav-item') const [adminClass, setAdminClass] = useState('header__nav-item') const [loginClass, setLoginClass] = useState('header__nav-item') + const [isLoggedIn, setIsLoggedIn] = useState(false) const router = useRouter() + // Checks if user is logged in by checking cookies + useEffect(() => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + console.log(isLoggedIn ? 'User is logged in' : 'User is not logged in') + }, []) + + const updateLoginStatus = () => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + } + const handleHomeClick = () => { setHomeClass('header__nav-item menu-active') setMoviesClass('header__nav-item') @@ -91,10 +106,16 @@ export default function NavMenu() {
  • { - handleLoginClick() + if (isLoggedIn) { + router.push('/login/account') + updateLoginStatus() + } else { + handleLoginClick() + updateLoginStatus() + } }} > - LOGIN + {isLoggedIn ? 'KONTO' : 'LOGIN'}
  • From 56cc603a45bbaed76706ed51cee8051929a9bb09 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:16:19 +0200 Subject: [PATCH 65/88] Added basic error text styling to login page --- src/styles/loginPage.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/loginPage.scss b/src/styles/loginPage.scss index cdea79d..10ffe3a 100644 --- a/src/styles/loginPage.scss +++ b/src/styles/loginPage.scss @@ -46,6 +46,10 @@ color: #295377; } + &__error_text { + color: red; + } + &__form_password { color: #295377; } From 317c76a4a3a2194e70206195ae8307a9a0bf1253 Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:23:39 +0200 Subject: [PATCH 66/88] Added user management functions including creation, retrieval, deletion, and password update --- src/lib/db/userDbService.js | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/lib/db/userDbService.js diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js new file mode 100644 index 0000000..0837b43 --- /dev/null +++ b/src/lib/db/userDbService.js @@ -0,0 +1,60 @@ +import User from './models/User' +import connectDB from './connectDB' + +// Work in progress: testing. +export async function getAllUsers() { + await connectDB() + return await User.find().select('Username') +} + +// Work in progress: testing. +export async function findUserByUsername(username) { + console.log(`Finding user with username: ${username}`) + await connectDB() + + // Work in progress: testing. + const findUser = await User.findOne({ Username: username }) + console.log(`User found: ${findUser ? findUser.Username : 'No user found'}`) + if (!findUser) { + console.log(`User with username ${username} not found.`) + throw new Error('Användaren kunde inte hittas.') + } + return findUser +} + +// Creates a new user. +// Password is in plain text, work in progress to hash it. +export async function createUser(username, password) { + console.log(`Creating user with username: ${username} and password: ${password}`) + + await connectDB() + + const existingUser = await User.findOne({ Username: username }) + if (username && existingUser) { + console.log(`User with username ${username} already exists.`) + throw new Error('Användarnamnet finns redan, vänligen välj ett annat.') + } + + const newUser = await User.create({ Username: username, Password: password }) + console.log(`User created successfully: ${newUser.Username}`) + return newUser +} + +// Work in progress: testing. +export async function deleteUserByUsername(username) { + await connectDB() + return await User.findOneAndDelete({ Username: username }) +} + +// Work in progress: testing. +export async function updateUserPassword(username, newPassword) { + await connectDB() + return await User.findOneAndUpdate({ Username: username }, { Password: newPassword }, { new: true }) +} + +// Work in progress: testing. +export async function userExists(username) { + await connectDB() + const user = await User.findOne({ Username: username }) + return !!user +} From 93157854728825b85f0f23245808266bdb1ec4c9 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:22:28 +0200 Subject: [PATCH 67/88] Added test page for login --- src/app/login/secret/page.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx new file mode 100644 index 0000000..60ec8c6 --- /dev/null +++ b/src/app/login/secret/page.jsx @@ -0,0 +1,19 @@ +import { cookies } from 'next/headers' + +export default function Secret() { + const allCookies = cookies() + const username = allCookies.get('Username')?.value ?? null + + if (username) { + console.log(`User ${username} is logged in`) + return ( +
    +

    User {username} logged in!

    +

    Hello {username}!

    +
    + ) + } else { + console.log('User is not logged in') + return

    You are not logged in!

    + } +} From 15b8c38188d1a51c5249c59bc8712a415a9be6f0 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:30:44 +0200 Subject: [PATCH 68/88] Fixed bug navigating to secret page before verifying if login is ok --- src/app/login/secret/page.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 60ec8c6..90e5ed1 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,7 +1,7 @@ import { cookies } from 'next/headers' -export default function Secret() { - const allCookies = cookies() +export default async function Secret() { + const allCookies = await cookies() const username = allCookies.get('Username')?.value ?? null if (username) { From 90a2d73463e0ca138ed0c2b17e68cf37bef81832 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 29 May 2025 12:42:01 +0200 Subject: [PATCH 69/88] Added logout button to clear auth cookie and navigating to login page --- src/app/login/secret/page.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx index 90e5ed1..76c792d 100644 --- a/src/app/login/secret/page.jsx +++ b/src/app/login/secret/page.jsx @@ -1,4 +1,5 @@ import { cookies } from 'next/headers' +import LogoutButton from '@/components/LogoutButton' export default async function Secret() { const allCookies = await cookies() @@ -10,6 +11,7 @@ export default async function Secret() {

    User {username} logged in!

    Hello {username}!

    +
    ) } else { From bf4546a45d363ac305591916601135cfefb951ea Mon Sep 17 00:00:00 2001 From: Calle Date: Mon, 2 Jun 2025 11:12:05 +0200 Subject: [PATCH 70/88] Remove Secret page that is now the account page --- src/app/login/secret/page.jsx | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/app/login/secret/page.jsx diff --git a/src/app/login/secret/page.jsx b/src/app/login/secret/page.jsx deleted file mode 100644 index 76c792d..0000000 --- a/src/app/login/secret/page.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { cookies } from 'next/headers' -import LogoutButton from '@/components/LogoutButton' - -export default async function Secret() { - const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - - if (username) { - console.log(`User ${username} is logged in`) - return ( -
    -

    User {username} logged in!

    -

    Hello {username}!

    - -
    - ) - } else { - console.log('User is not logged in') - return

    You are not logged in!

    - } -} From ad8857c9ac107c111fceec049ef86a532e479085 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:41:04 +0200 Subject: [PATCH 71/88] Added admin user management functionality and update admin panel navigation --- src/app/admin/page.jsx | 28 ++++- src/components/admin/AdminCreateUser.jsx | 130 +++++++++++++++++++++++ src/components/admin/AdminTabNav.jsx | 3 + 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/components/admin/AdminCreateUser.jsx diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index 42549f6..e46ee2f 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -1,6 +1,5 @@ 'use client' - -import { useState } from 'react' +import { useState, useEffect } from 'react' import AdminRoomForm from '@/components/admin/AdminRoomForm' import AdminScreeningForm from '@/components/admin/AdminScreeningForm' import AdminMovieForm from '@/components/admin/AdminMovieForm' @@ -12,10 +11,20 @@ import { useAdminData } from '@/hooks/useAdminData' import AdminTabNav from '@/components/admin/AdminTabNav' import AdminRoomList from '@/components/admin/AdminRoomList' import AdminBookingPanel from '@/components/admin/AdminBookingPanel' +import AdminCreateUser from '@/components/admin/AdminCreateUser' export default function AdminPanel() { + const [isAdmin, setIsAdmin] = useState(false) + const [checked, setChecked] = useState(false) const [activeTab, setActiveTab] = useState('list') + useEffect(() => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const adminCookie = cookies.find((c) => c.startsWith('Admin=')) + setIsAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + setChecked(true) + }, []) + const { movies, screenings, @@ -42,6 +51,14 @@ export default function AdminPanel() { loading, } = useAdminData() + if (!checked) { + return
    Laddar...
    + } + + if (!isAdmin) { + return

    Du är inte admin!

    + } + return (
    @@ -128,6 +145,13 @@ export default function AdminPanel() {
    )} + {activeTab === 'user1' && ( + <> +
    + +
    + + )}
    {successMessage && setSuccessMessage(null)} />} diff --git a/src/components/admin/AdminCreateUser.jsx b/src/components/admin/AdminCreateUser.jsx new file mode 100644 index 0000000..a36911a --- /dev/null +++ b/src/components/admin/AdminCreateUser.jsx @@ -0,0 +1,130 @@ +'use client' +import { useState } from 'react' + +export default function AdminCreate() { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [success, setSuccess] = useState(null) + const [users, setUsers] = useState([]) // <-- Lägg till denna + const [userError, setUserError] = useState(null) + + async function handleSubmit(e) { + e.preventDefault() + setError(null) + setSuccess(null) + try { + const response = await fetch('/api/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ Username: username, Password: password }), + }) + if (!response.ok) throw new Error('Failed to create user') + setSuccess('Användare skapad!') + setUsername('') + setPassword('') + } catch (err) { + setError('Kunde inte skapa användare') + } + } + + async function fetchUsers() { + setUserError(null) + try { + const response = await fetch('/api/getusers') + if (!response.ok) throw new Error('Kunde inte hämta användare') + const data = await response.json() + setUsers(data) + } catch (err) { + setUserError('Kunde inte hämta användare') + } + } + + async function handleToggleAdmin(username) { + try { + const res = await fetch('/api/toggleadmin', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username }), + }) + if (!res.ok) throw new Error('Kunde inte toggla admin') + // Uppdatera användarlistan efter toggling + fetchUsers() + } catch (err) { + setUserError('Kunde inte toggla admin') + } + } + + async function handleDeleteUser(username) { + try { + const res = await fetch('/api/deleteuser', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username }), + }) + if (!res.ok) throw new Error('Kunde inte ta bort användare') + fetchUsers() + } catch (err) { + setUserError('Kunde inte ta bort användare') + } + } + + return ( +
    +
    +

    Skapa konto

    +
    + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
    + {error &&
    {error}
    } + {success &&
    {success}
    } +
    +
    +
    +

    Användare

    +

    Här kan du hantera befintliga användare.

    + +
    + {userError &&
    {userError}
    } +
    + + {Array.isArray(users) && + users.map((user) => ( + //
    +

    + {user.Username} {user.Admin === true || user.Admin === 'true' ? '(Admin)' : ''} + + +

    + //
    + ))} +
    +
    + ) +} diff --git a/src/components/admin/AdminTabNav.jsx b/src/components/admin/AdminTabNav.jsx index 3650d05..5a2c095 100644 --- a/src/components/admin/AdminTabNav.jsx +++ b/src/components/admin/AdminTabNav.jsx @@ -18,6 +18,9 @@ export default function AdminTabNav({ activeTab, setActiveTab }) { + ) } From e1405c1352e6ef3da971291102efd36baa44d601 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:42:04 +0200 Subject: [PATCH 72/88] Updated login API to include admin status in response cookies --- src/app/api/login/route.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index e3c0e66..ca531f6 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -13,10 +13,13 @@ export async function POST(request) { let login = await findUserByUsername(payload.Username) console.log(`${login.Username} found in the database`) + console.log(`Is ${payload.Username} an admin? ${login.Admin}`) + if (login.Username === payload.Username && login.Password === payload.Password) { console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) response.cookies.set('Username', payload.Username, {}) + response.cookies.set('Admin', login.Admin ? 'true' : 'false', {}) return response } else { console.log(`Login failed for username: ${payload.Username}`) From 871d8650c4bee3339a80c84c50d6f0e8fe40962d Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:42:50 +0200 Subject: [PATCH 73/88] Added admin cookie clearing on logout --- src/app/api/logout/route.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index a2cbb9f..dd77e3a 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -3,6 +3,7 @@ import { cookies } from 'next/headers' export async function POST() { const storeCookie = await cookies() storeCookie.set('Username', '') + storeCookie.set('Admin', '') return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, From 9e70e9628260c7b4cad88100e7c46a600baeb59c Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:44:22 +0200 Subject: [PATCH 74/88] Updated user management and admin status in user retrieval and creation functions, and add toggleadmin functionality --- src/lib/db/userDbService.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js index 0837b43..5a9b338 100644 --- a/src/lib/db/userDbService.js +++ b/src/lib/db/userDbService.js @@ -4,7 +4,7 @@ import connectDB from './connectDB' // Work in progress: testing. export async function getAllUsers() { await connectDB() - return await User.find().select('Username') + return await User.find().select('Username Admin') } // Work in progress: testing. @@ -12,8 +12,7 @@ export async function findUserByUsername(username) { console.log(`Finding user with username: ${username}`) await connectDB() - // Work in progress: testing. - const findUser = await User.findOne({ Username: username }) + const findUser = await User.findOne({ Username: username }).select('Username Password Admin') console.log(`User found: ${findUser ? findUser.Username : 'No user found'}`) if (!findUser) { console.log(`User with username ${username} not found.`) @@ -35,7 +34,7 @@ export async function createUser(username, password) { throw new Error('Användarnamnet finns redan, vänligen välj ett annat.') } - const newUser = await User.create({ Username: username, Password: password }) + const newUser = await User.create({ Username: username, Password: password, Admin: false }) console.log(`User created successfully: ${newUser.Username}`) return newUser } @@ -58,3 +57,15 @@ export async function userExists(username) { const user = await User.findOne({ Username: username }) return !!user } + +export async function toggleAdmin(username) { + await connectDB() + const user = await User.findOne({ Username: username }) + if (!user) { + throw new Error(`User with username ${username} not found.`) + } + console.log(`Toggling admin status for user: ${username}. Current admin status: ${user.Admin}`) + user.Admin = !user.Admin + await user.save() + return user +} From 3db8bb5a6f993a44993ea802dcb9fecbc281c0dd Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:44:48 +0200 Subject: [PATCH 75/88] Added toggle admin functionality with POST endpoint --- src/app/api/toggleadmin/route.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/app/api/toggleadmin/route.js diff --git a/src/app/api/toggleadmin/route.js b/src/app/api/toggleadmin/route.js new file mode 100644 index 0000000..2182c8e --- /dev/null +++ b/src/app/api/toggleadmin/route.js @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server' +import { toggleAdmin } from '@/lib/db/userDbService' + +export async function POST(request) { + const { username } = await request.json() + try { + const user = await toggleAdmin(username) + return NextResponse.json({ success: true, user }) + } catch (err) { + return NextResponse.json({ success: false, error: err.message }, { status: 500 }) + } +} From d11602b199ee6b0aab0e6536163f31aec2490270 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:45:04 +0200 Subject: [PATCH 76/88] Added POST endpoint for user deletion functionality --- src/app/api/deleteuser/route.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/api/deleteuser/route.js diff --git a/src/app/api/deleteuser/route.js b/src/app/api/deleteuser/route.js new file mode 100644 index 0000000..9f93492 --- /dev/null +++ b/src/app/api/deleteuser/route.js @@ -0,0 +1,17 @@ +import { deleteUserByUsername } from '@/lib/db/userDbService' + +export async function POST(request) { + const { username } = await request.json() + + if (!username) { + return new Response('Username is required', { status: 400 }) + } + + try { + await deleteUserByUsername(username) + return new Response(`User ${username} deleted successfully`, { status: 200 }) + } catch (error) { + console.error('Error deleting user:', error) + return new Response('Failed to delete user', { status: 500 }) + } +} From 6c0741e55fbe2517b6c425bde497bc8101ebe9da Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:45:39 +0200 Subject: [PATCH 77/88] Added GET endpoint for retrieving users with admin authorization --- src/app/api/getusers/route.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/api/getusers/route.js diff --git a/src/app/api/getusers/route.js b/src/app/api/getusers/route.js new file mode 100644 index 0000000..54eb08f --- /dev/null +++ b/src/app/api/getusers/route.js @@ -0,0 +1,17 @@ +import { NextResponse } from 'next/server' +import connectDB from '@/lib/db/connectDB' +import { getAllUsers } from '@/lib/db/userDbService' + +export async function GET(request) { + if (!request.cookies.get('Admin') || request.cookies.get('Admin').value !== 'true') { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + try { + await connectDB() + const users = await getAllUsers() + return NextResponse.json(users, { status: 200 }) + } catch (error) { + console.error('Error fetching users:', error) + return NextResponse.json({ error: 'Failed to fetch users' }, { status: 500 }) + } +} From e0843eb701cb436ea1e9dc8c2979bfed70f083c3 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:46:14 +0200 Subject: [PATCH 78/88] Added POST endpoint for changing user password --- src/app/api/changepassword/route.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/app/api/changepassword/route.js diff --git a/src/app/api/changepassword/route.js b/src/app/api/changepassword/route.js new file mode 100644 index 0000000..c99b890 --- /dev/null +++ b/src/app/api/changepassword/route.js @@ -0,0 +1,18 @@ +import { updateUserPassword } from '@/lib/db/userDbService' +import { NextResponse } from 'next/server' +export async function POST(request) { + console.log(request.Username) + const payload = await request.json() + + console.log(`Received request to update password for user: ${payload.Username}`) + + try { + const updatedUser = await updateUserPassword(payload.Username, payload.Password) + + console.log(`Password updated successfully for user: ${updatedUser.Username}`) + return NextResponse.json({ message: 'Password updated successfully' }, { status: 200 }) + } catch (error) { + console.error(`Error updating password for user ${username}:`, error) + return NextResponse.json({ error: 'Failed to update password' }, { status: 500 }) + } +} From 98d7d3187aff9c5e8ddd4d33eeefb6033582a41d Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:47:05 +0200 Subject: [PATCH 79/88] Added change password page --- src/app/forgotpassword/page.jsx | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/app/forgotpassword/page.jsx diff --git a/src/app/forgotpassword/page.jsx b/src/app/forgotpassword/page.jsx new file mode 100644 index 0000000..696a7d9 --- /dev/null +++ b/src/app/forgotpassword/page.jsx @@ -0,0 +1,57 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' + +export default function Register() { + const [Username, setUsername] = useState('') + const [Password, setPassword] = useState('') + const [Error, setError] = useState(false) + const router = useRouter() + + return ( +
    { + ev.preventDefault() + const response = await fetch('/api/changepassword', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + Username, + Password, + }), + }) + if (response.ok) { + router.push('/login') + } else { + setError(true) + } + }} + > +

    Glömt lösenord

    +

    Användarnamn

    + setUsername(ev.target.value)} + /> +

    Nytt lösenord

    + setPassword(ev.target.value)} + /> + +
    + ) +} From 027f7343bc3a8eba0eafc2aee633e89ccfbbe8f4 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:49:52 +0200 Subject: [PATCH 80/88] Updated login page,register page and account page translating some words to swedish from english to fit the website --- src/app/login/account/page.jsx | 16 ++++++++-------- src/app/login/page.jsx | 14 +++++++++++--- src/app/register/page.jsx | 10 ++++++++-- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx index fe2b882..e59f845 100644 --- a/src/app/login/account/page.jsx +++ b/src/app/login/account/page.jsx @@ -6,22 +6,22 @@ export default async function Secret() { const username = allCookies.get('Username')?.value ?? null if (username) { - console.log(`User ${username} is logged in`) return (
    -

    User {username} logged in!

    -

    Hello {username}!

    +

    Användare {username} har loggat in!

    +

    Hej {username}!

    -
    -

    Button

    - -
    ) } else { console.log('User is not logged in') - return

    You are not logged in!

    + return ( +
    +

    Du är inte inloggad!

    + Logga in +
    + ) } } diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 8d552b7..7957c12 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -11,7 +11,7 @@ export default function Login() { return (
    -

    Login

    +

    Logga in

    Registrera här
    -

    Username

    +

    Användarnamn

    setUsername(ev.target.value)} /> -

    Password

    +

    Lösenord

    setPassword(ev.target.value)} /> + Glömt lösenordet? diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx index cc1695b..04ba256 100644 --- a/src/app/register/page.jsx +++ b/src/app/register/page.jsx @@ -32,17 +32,23 @@ export default function Register() { > {Error &&

    Användaren finns redan

    }

    Registrera konto

    -

    Username

    +

    Användarnamn

    setUsername(ev.target.value)} /> -

    Password

    +

    Lösenord

    setPassword(ev.target.value)} /> From cceac5a4a19e1800d5bc3a28fe11e57f016efa33 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:50:11 +0200 Subject: [PATCH 81/88] Added Admin field to user schema with default value set to false --- src/lib/db/models/User.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/db/models/User.js b/src/lib/db/models/User.js index 63f83e6..78bf249 100644 --- a/src/lib/db/models/User.js +++ b/src/lib/db/models/User.js @@ -3,6 +3,7 @@ import mongoose from 'mongoose' const userSchema = new mongoose.Schema({ Username: { type: String, required: true }, Password: { type: String, required: true }, + Admin: { type: Boolean, default: false }, }) export default mongoose.models.User || mongoose.model('User', userSchema) From 0f0f52af408930bb2ea0d793529e9f8afecb3b45 Mon Sep 17 00:00:00 2001 From: Calle Date: Wed, 4 Jun 2025 10:51:18 +0200 Subject: [PATCH 82/88] Updated LogoutButton and NavMenu to improve login status handling and UI updates --- src/components/LogoutButton.jsx | 7 ++++- src/components/NavMenu.jsx | 45 +++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx index a2e7047..78f2520 100644 --- a/src/components/LogoutButton.jsx +++ b/src/components/LogoutButton.jsx @@ -6,8 +6,13 @@ export default function LogoutButton() { async function handleLogout() { await fetch('/api/logout', { method: 'POST' }) + window.dispatchEvent(new Event('loginStatusChanged')) router.push('/login') } - return + return ( + + ) } diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 5ecc716..77281f4 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,6 +1,6 @@ 'use client' import { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' +import { useRouter, usePathname } from 'next/navigation' export default function NavMenu() { const [homeClass, setHomeClass] = useState('header__nav-item') @@ -9,22 +9,24 @@ export default function NavMenu() { const [adminClass, setAdminClass] = useState('header__nav-item') const [loginClass, setLoginClass] = useState('header__nav-item') const [isLoggedIn, setIsLoggedIn] = useState(false) + const [Admin, setAdmin] = useState(false) const router = useRouter() + const pathname = usePathname() // Checks if user is logged in by checking cookies useEffect(() => { - const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - console.log(isLoggedIn ? 'User is logged in' : 'User is not logged in') + const checkLogin = () => { + const cookies = document.cookie.split(';').map((c) => c.trim()) + const usernameCookie = cookies.find((c) => c.startsWith('Username=')) + setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) + const adminCookie = cookies.find((c) => c.startsWith('Admin=')) + setAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + } + checkLogin() + window.addEventListener('loginStatusChanged', checkLogin) + return () => window.removeEventListener('loginStatusChanged', checkLogin) }, []) - const updateLoginStatus = () => { - const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - } - const handleHomeClick = () => { setHomeClass('header__nav-item menu-active') setMoviesClass('header__nav-item') @@ -93,7 +95,7 @@ export default function NavMenu() { > OM OSS - {process.env.NODE_ENV === 'development' && ( + {(process.env.NODE_ENV === 'development' || Admin === true) && (
  • { @@ -104,20 +106,31 @@ export default function NavMenu() {
  • )}
  • { if (isLoggedIn) { + setHomeClass('header__nav-item') + setMoviesClass('header__nav-item') + setAboutClass('header__nav-item') + setAdminClass('header__nav-item') + setLoginClass('header__nav-item menu-active') router.push('/login/account') - updateLoginStatus() } else { handleLoginClick() - updateLoginStatus() } }} > - {isLoggedIn ? 'KONTO' : 'LOGIN'} + {isLoggedIn ? 'KONTO' : 'LOGGA IN'}
  • + {process.env.NODE_ENV === 'development' && + console.log(`isLoggedIn:${isLoggedIn} NODE_ENV:${process.env.NODE_ENV} Admin:${Admin}`)} ) } From 153ce01ca996b70d55353399a8e36b47af47bdc4 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 12:31:58 +0200 Subject: [PATCH 83/88] Adding jsonwebtoken --- package-lock.json | 103 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 523a675..370c333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", @@ -2289,6 +2290,12 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "dev": true, @@ -2871,6 +2878,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.155", "dev": true, @@ -5412,6 +5428,28 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "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", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -5426,6 +5464,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.6.3", "license": "Apache-2.0", @@ -5692,11 +5751,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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.merge": { "version": "4.6.2", "dev": true, "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==", + "license": "MIT" + }, "node_modules/log-update": { "version": "6.1.0", "dev": true, @@ -6984,7 +7085,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "dev": true, "funding": [ { "type": "github", @@ -7057,7 +7157,6 @@ }, "node_modules/semver": { "version": "7.7.2", - "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index c64ee64..6b082b3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "dotenv": "^16.5.0", "formdata-node": "^6.0.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", From e2343084b1f189f5f58879f681aab7d031fe080a Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 12:55:27 +0200 Subject: [PATCH 84/88] Adding jwt-decode for jsonwebtoken handling --- package-lock.json | 10 ++++++++++ package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index 370c333..f85d8d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", @@ -5485,6 +5486,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/kareem": { "version": "2.6.3", "license": "Apache-2.0", diff --git a/package.json b/package.json index 6b082b3..1804c63 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "mongoose": "^8.15.0", "next": "15.3.1", "react": "^19.0.0", From bbeb1ba4ac8f46eb9a8f04e72d3858aba2d456d1 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 13:59:15 +0200 Subject: [PATCH 85/88] Updated login/logout, account and admin page to work with JWT --- src/app/admin/page.jsx | 19 +++++++++++++++++-- src/app/api/getusers/route.js | 17 +++++++++++++++-- src/app/api/login/route.js | 8 ++++++-- src/app/api/logout/route.js | 3 +-- src/app/login/account/page.jsx | 23 ++++++++++++++++++----- src/components/NavMenu.jsx | 23 +++++++++++++++++++---- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index e46ee2f..d5798b2 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -12,6 +12,7 @@ import AdminTabNav from '@/components/admin/AdminTabNav' import AdminRoomList from '@/components/admin/AdminRoomList' import AdminBookingPanel from '@/components/admin/AdminBookingPanel' import AdminCreateUser from '@/components/admin/AdminCreateUser' +import { jwtDecode } from 'jwt-decode' export default function AdminPanel() { const [isAdmin, setIsAdmin] = useState(false) @@ -20,8 +21,22 @@ export default function AdminPanel() { useEffect(() => { const cookies = document.cookie.split(';').map((c) => c.trim()) - const adminCookie = cookies.find((c) => c.startsWith('Admin=')) - setIsAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + if (jwtCookie) { + console.log('JWT cookie found:', jwtCookie) + const token = jwtCookie.split('=')[1] + try { + console.log('JWT token found:', token) + const decoded = jwtDecode(token) + setIsAdmin(decoded.admin) + } catch (e) { + console.error('Invalid JWT token:', e) + setIsAdmin(false) + } + } else { + console.log('JWT cookie not found') + setIsAdmin(false) + } setChecked(true) }, []) diff --git a/src/app/api/getusers/route.js b/src/app/api/getusers/route.js index 54eb08f..5bc925f 100644 --- a/src/app/api/getusers/route.js +++ b/src/app/api/getusers/route.js @@ -1,11 +1,24 @@ import { NextResponse } from 'next/server' import connectDB from '@/lib/db/connectDB' import { getAllUsers } from '@/lib/db/userDbService' - +import { jwtDecode } from 'jwt-decode' export async function GET(request) { - if (!request.cookies.get('Admin') || request.cookies.get('Admin').value !== 'true') { + const cookieHeader = request.headers.get('cookie') || '' + const cookies = cookieHeader.split(';').map((c) => c.trim()) + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + if (!jwtCookie) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } + + const token = jwtCookie.split('=')[1] + try { + const decoded = jwtDecode(token) + if (!decoded.admin) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) + } + } catch (error) { + return NextResponse.json({ error: 'Invalid token' }, { status: 401 }) + } try { await connectDB() const users = await getAllUsers() diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index ca531f6..8fa6a1a 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -1,6 +1,7 @@ import connectDB from '@/lib/db/connectDB' import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' +import jsonwebtoken from 'jsonwebtoken' export async function POST(request) { const payload = await request.json() @@ -18,8 +19,11 @@ export async function POST(request) { if (login.Username === payload.Username && login.Password === payload.Password) { console.log(`User ${payload.Username} found in the database`) const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) - response.cookies.set('Username', payload.Username, {}) - response.cookies.set('Admin', login.Admin ? 'true' : 'false', {}) + + const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { + expiresIn: '1h', + }) + response.cookies.set('JWT', jwtToken, { httpOnly: false }) return response } else { console.log(`Login failed for username: ${payload.Username}`) diff --git a/src/app/api/logout/route.js b/src/app/api/logout/route.js index dd77e3a..c28acb1 100644 --- a/src/app/api/logout/route.js +++ b/src/app/api/logout/route.js @@ -2,8 +2,7 @@ import { cookies } from 'next/headers' export async function POST() { const storeCookie = await cookies() - storeCookie.set('Username', '') - storeCookie.set('Admin', '') + storeCookie.delete('JWT', { httpOnly: false }) return new Response(JSON.stringify({ message: 'Logged out' }), { status: 200, headers: { 'Content-Type': 'application/json' }, diff --git a/src/app/login/account/page.jsx b/src/app/login/account/page.jsx index e59f845..990c2e1 100644 --- a/src/app/login/account/page.jsx +++ b/src/app/login/account/page.jsx @@ -1,16 +1,29 @@ import { cookies } from 'next/headers' import LogoutButton from '@/components/LogoutButton' +import { jwtDecode } from 'jwt-decode' export default async function Secret() { const allCookies = await cookies() - const username = allCookies.get('Username')?.value ?? null - + const jwtCookie = allCookies.get('JWT') + let username = null + if (jwtCookie) { + try { + const decoded = jwtDecode(jwtCookie.value) + username = decoded.username + } catch (e) { + console.error('Invalid JWT token:', e) + } + } if (username) { return (
    -
    -

    Användare {username} har loggat in!

    -

    Hej {username}!

    +
    +
    +

    Användare {username} har loggat in!

    +
    +
    +

    Hej {username}!

    +
    diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 77281f4..83f3600 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' import { useRouter, usePathname } from 'next/navigation' +import { jwtDecode } from 'jwt-decode' export default function NavMenu() { const [homeClass, setHomeClass] = useState('header__nav-item') @@ -17,10 +18,24 @@ export default function NavMenu() { useEffect(() => { const checkLogin = () => { const cookies = document.cookie.split(';').map((c) => c.trim()) - const usernameCookie = cookies.find((c) => c.startsWith('Username=')) - setIsLoggedIn(!!usernameCookie && usernameCookie.split('=')[1]) - const adminCookie = cookies.find((c) => c.startsWith('Admin=')) - setAdmin(adminCookie && adminCookie.split('=')[1] === 'true') + const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) + console.log('Checking login status...') + console.log('Cookies:', cookies) + console.log('JWT Cookie:', jwtCookie) + if (jwtCookie) { + const token = jwtCookie.split('=')[1] + try { + const decoded = jwtDecode(token) + setIsLoggedIn(!!decoded.username) + setAdmin(decoded.admin) + } catch (e) { + setIsLoggedIn(false) + setAdmin(false) + } + } else { + setIsLoggedIn(false) + setAdmin(false) + } } checkLogin() window.addEventListener('loginStatusChanged', checkLogin) From d4ee9c98d038560f355ee1da94527a8fc9698895 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:11:08 +0200 Subject: [PATCH 86/88] Adding bcrypt for password hashing --- package-lock.json | 35 +++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 36 insertions(+) diff --git a/package-lock.json b/package-lock.json index f85d8d7..01021b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "kino-project", "version": "0.1.0", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", @@ -2225,6 +2226,29 @@ "dev": true, "license": "MIT" }, + "node_modules/bcrypt": { + "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": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -6280,6 +6304,17 @@ "license": "MIT", "optional": true }, + "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", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "dev": true, diff --git a/package.json b/package.json index 1804c63..4971318 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "watch-css": "sass --watch src/styles/main.scss:public/dist/styles.css" }, "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "formdata-node": "^6.0.3", "jsonwebtoken": "^9.0.2", From a2035b18fcc143b584b53467ea8e84dd29a892c8 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:15:30 +0200 Subject: [PATCH 87/88] Added password hashing for user registration and login & updated forgot my password logic --- src/app/api/changepassword/route.js | 2 +- src/app/api/login/route.js | 31 +++++++++++++++++------------ src/app/api/register/route.js | 8 ++++++-- src/lib/db/userDbService.js | 9 +++++---- src/lib/utils/hashPassword.js | 7 +++++++ 5 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 src/lib/utils/hashPassword.js diff --git a/src/app/api/changepassword/route.js b/src/app/api/changepassword/route.js index c99b890..7d3f96c 100644 --- a/src/app/api/changepassword/route.js +++ b/src/app/api/changepassword/route.js @@ -1,7 +1,7 @@ import { updateUserPassword } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' + export async function POST(request) { - console.log(request.Username) const payload = await request.json() console.log(`Received request to update password for user: ${payload.Username}`) diff --git a/src/app/api/login/route.js b/src/app/api/login/route.js index 8fa6a1a..a1f8fd2 100644 --- a/src/app/api/login/route.js +++ b/src/app/api/login/route.js @@ -2,6 +2,8 @@ import connectDB from '@/lib/db/connectDB' import { findUserByUsername } from '@/lib/db/userDbService' import { NextResponse } from 'next/server' import jsonwebtoken from 'jsonwebtoken' +import hashPassword from '@/lib/utils/hashPassword' +import bcrypt from 'bcrypt' export async function POST(request) { const payload = await request.json() @@ -13,20 +15,23 @@ export async function POST(request) { await connectDB() let login = await findUserByUsername(payload.Username) - console.log(`${login.Username} found in the database`) - console.log(`Is ${payload.Username} an admin? ${login.Admin}`) + const secretpassword = await hashPassword(payload.Password) - if (login.Username === payload.Username && login.Password === payload.Password) { + if (login.Username == payload.Username) { console.log(`User ${payload.Username} found in the database`) - const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) - - const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { - expiresIn: '1h', - }) - response.cookies.set('JWT', jwtToken, { httpOnly: false }) - return response - } else { - console.log(`Login failed for username: ${payload.Username}`) - return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) + const isMatch = await bcrypt.compare(payload.Password, login.Password) + console.log(`Password match for user ${payload.Username}: ${isMatch}`) + if (isMatch) { + const response = NextResponse.json({ message: 'Login successful' }, { status: 200 }) + const jwtToken = jsonwebtoken.sign({ username: payload.Username, admin: login.Admin }, process.env.JWT_SECRET, { + expiresIn: '1h', + }) + response.cookies.set('JWT', jwtToken, { httpOnly: false }) + return response + } else login.Password !== secretpassword + { + console.log(`Password incorrect for user: ${payload.Username}`) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) + } } } diff --git a/src/app/api/register/route.js b/src/app/api/register/route.js index 46c8500..0b69006 100644 --- a/src/app/api/register/route.js +++ b/src/app/api/register/route.js @@ -1,15 +1,19 @@ import { NextResponse } from 'next/server' import { createUser } from '@/lib/db/userDbService' +import bcrypt from 'bcrypt' export async function POST(request) { const payload = await request.json() - // Log the payload for debugging + // Hash the password before storing it console.log(`Registering user with username: ${payload.Username}`) + const salt = await bcrypt.genSalt(15) + const hash = await bcrypt.hash(payload.Password, salt) + const hashedPassword = hash // Create a new user in the database try { - const newUser = await createUser(payload.Username, payload.Password) + const newUser = await createUser(payload.Username, hashedPassword) console.log(`User created successfully: ${newUser.Username}`) return NextResponse.json({ message: 'User registered successfully' }, { status: 201 }) } catch (error) { diff --git a/src/lib/db/userDbService.js b/src/lib/db/userDbService.js index 5a9b338..e89c64a 100644 --- a/src/lib/db/userDbService.js +++ b/src/lib/db/userDbService.js @@ -1,15 +1,16 @@ import User from './models/User' import connectDB from './connectDB' +import hashPassword from '@/lib/utils/hashPassword' // Work in progress: testing. export async function getAllUsers() { await connectDB() + console.log('Fetching all users from the database...') return await User.find().select('Username Admin') } // Work in progress: testing. export async function findUserByUsername(username) { - console.log(`Finding user with username: ${username}`) await connectDB() const findUser = await User.findOne({ Username: username }).select('Username Password Admin') @@ -24,8 +25,7 @@ export async function findUserByUsername(username) { // Creates a new user. // Password is in plain text, work in progress to hash it. export async function createUser(username, password) { - console.log(`Creating user with username: ${username} and password: ${password}`) - + hashPassword(password) await connectDB() const existingUser = await User.findOne({ Username: username }) @@ -35,19 +35,20 @@ export async function createUser(username, password) { } const newUser = await User.create({ Username: username, Password: password, Admin: false }) - console.log(`User created successfully: ${newUser.Username}`) return newUser } // Work in progress: testing. export async function deleteUserByUsername(username) { await connectDB() + console.log(`Attempting to delete user with username: ${username}`) return await User.findOneAndDelete({ Username: username }) } // Work in progress: testing. export async function updateUserPassword(username, newPassword) { await connectDB() + newPassword = await hashPassword(newPassword) return await User.findOneAndUpdate({ Username: username }, { Password: newPassword }, { new: true }) } diff --git a/src/lib/utils/hashPassword.js b/src/lib/utils/hashPassword.js new file mode 100644 index 0000000..a80c4fa --- /dev/null +++ b/src/lib/utils/hashPassword.js @@ -0,0 +1,7 @@ +import bcrypt from 'bcrypt' + +export default async function hashPassword(password) { + const salt = await bcrypt.genSalt(15) + const hashedPassword = await bcrypt.hash(password, salt) + return hashedPassword +} From 5d1966c32c728003ef4e00a79522ffe824803a24 Mon Sep 17 00:00:00 2001 From: Calle Date: Thu, 5 Jun 2025 15:22:28 +0200 Subject: [PATCH 88/88] Removed some testing logs --- src/app/admin/page.jsx | 4 ---- src/components/NavMenu.jsx | 3 --- src/components/admin/AdminCreateUser.jsx | 2 -- 3 files changed, 9 deletions(-) diff --git a/src/app/admin/page.jsx b/src/app/admin/page.jsx index d5798b2..c0d7f6f 100644 --- a/src/app/admin/page.jsx +++ b/src/app/admin/page.jsx @@ -23,18 +23,14 @@ export default function AdminPanel() { const cookies = document.cookie.split(';').map((c) => c.trim()) const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) if (jwtCookie) { - console.log('JWT cookie found:', jwtCookie) const token = jwtCookie.split('=')[1] try { - console.log('JWT token found:', token) const decoded = jwtDecode(token) setIsAdmin(decoded.admin) } catch (e) { - console.error('Invalid JWT token:', e) setIsAdmin(false) } } else { - console.log('JWT cookie not found') setIsAdmin(false) } setChecked(true) diff --git a/src/components/NavMenu.jsx b/src/components/NavMenu.jsx index 83f3600..8ec424d 100644 --- a/src/components/NavMenu.jsx +++ b/src/components/NavMenu.jsx @@ -19,9 +19,6 @@ export default function NavMenu() { const checkLogin = () => { const cookies = document.cookie.split(';').map((c) => c.trim()) const jwtCookie = cookies.find((c) => c.startsWith('JWT=')) - console.log('Checking login status...') - console.log('Cookies:', cookies) - console.log('JWT Cookie:', jwtCookie) if (jwtCookie) { const token = jwtCookie.split('=')[1] try { diff --git a/src/components/admin/AdminCreateUser.jsx b/src/components/admin/AdminCreateUser.jsx index a36911a..ab75ed4 100644 --- a/src/components/admin/AdminCreateUser.jsx +++ b/src/components/admin/AdminCreateUser.jsx @@ -112,7 +112,6 @@ export default function AdminCreate() { {Array.isArray(users) && users.map((user) => ( - //

    {user.Username} {user.Admin === true || user.Admin === 'true' ? '(Admin)' : ''}

    - //
    ))}