diff --git a/client/src/App.svelte b/client/src/App.svelte index 3dda031..bc91de1 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -7,6 +7,7 @@ import { API_BASE } from './config.js'; import { applyTheme, currentTheme } from './stores/theme.js'; import { get } from 'svelte/store'; + import { getCookie } from './utils/cookies.js'; let isAuthenticated = false; let user = null; @@ -15,35 +16,18 @@ onMount(() => { applyTheme(get(currentTheme)); - - const storedAuth = localStorage.getItem('auth_token'); - const storedUser = localStorage.getItem('user_data'); - - if (storedAuth && storedUser) { - try { - isAuthenticated = true; - user = JSON.parse(storedUser); - loadSpaces(); - } catch (err) { - localStorage.removeItem('auth_token'); - localStorage.removeItem('user_data'); - } - } }); function handleAuthenticated(event) { const { authorization, username, email, is_admin } = event.detail; - + user = { authorization, username, email, is_admin }; - - localStorage.setItem('auth_token', authorization); - localStorage.setItem('user_data', JSON.stringify(user)); - + isAuthenticated = true; loadSpaces(); } @@ -82,13 +66,10 @@ console.error('Sign out error:', err); } } - + isAuthenticated = false; user = null; spaces = []; - - localStorage.removeItem('auth_token'); - localStorage.removeItem('user_data'); } diff --git a/client/src/utils/cookies.js b/client/src/utils/cookies.js new file mode 100644 index 0000000..ad96994 --- /dev/null +++ b/client/src/utils/cookies.js @@ -0,0 +1,10 @@ +export function getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); + return null; +} + +export function deleteCookie(name) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; +} diff --git a/package-lock.json b/package-lock.json index db816f2..f2d1844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "airtable": "^0.12.2", "body-parser": "^1.20.1", "concurrently": "^7.5.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dockerode": "^4.0.9", "dotenv": "^17.2.3", @@ -579,6 +580,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "license": "MIT" @@ -2593,6 +2614,22 @@ "cookie": { "version": "0.5.0" }, + "cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "requires": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + } + } + }, "cookie-signature": { "version": "1.0.6" }, diff --git a/package.json b/package.json index 53e9f32..42a4cfa 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "airtable": "^0.12.2", "body-parser": "^1.20.1", "concurrently": "^7.5.0", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dockerode": "^4.0.9", "dotenv": "^17.2.3", diff --git a/src/api/users/auth.route.js b/src/api/users/auth.route.js index ed5954b..cd9084b 100644 --- a/src/api/users/auth.route.js +++ b/src/api/users/auth.route.js @@ -127,7 +127,14 @@ router.post('/signup', /* authLimiter, */ async (req, res) => { is_admin: false }) .returning(['id', 'email', 'username', 'authorization', 'is_admin']); - + + res.cookie('auth_token', newUser.authorization, { + httpOnly: false, + maxAge: 7 * 24 * 60 * 60 * 1000, + sameSite: 'strict', + secure: process.env.NODE_ENV === 'production' + }); + res.status(201).json({ success: true, message: 'User created successfully', @@ -203,7 +210,14 @@ router.post('/login', /* authLimiter, */ async (req, res) => { .where('email', email) .update({ authorization: newAuthToken }) .returning(['email', 'username', 'authorization', 'is_admin']); - + + res.cookie('auth_token', updatedUser.authorization, { + httpOnly: false, + maxAge: 7 * 24 * 60 * 60 * 1000, + sameSite: 'strict', + secure: process.env.NODE_ENV === 'production' + }); + res.status(200).json({ success: true, message: 'Login successful', @@ -254,7 +268,9 @@ router.post('/signout', async (req, res) => { .where('authorization', authorization) .update({ authorization: newAuthToken }) .returning(['email']); - + + res.clearCookie('auth_token'); + res.status(200).json({ success: true, message: 'Sign out successful', diff --git a/src/app.js b/src/app.js index d33de9b..d4bba05 100644 --- a/src/app.js +++ b/src/app.js @@ -1,5 +1,6 @@ import express from 'express'; import cors from 'cors'; +import cookieParser from 'cookie-parser'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -12,6 +13,7 @@ import { notFound, errorHandler } from './middlewares/errors.middleware.js'; app.use(express.urlencoded({ extended: true })); app.use(express.json()); +app.use(cookieParser()); app.use(cors({ origin: process.env.FRONTEND_URL || 'http://localhost:5173', credentials: true,