feat: enable cookie auth flow and database url runtime config
This commit is contained in:
parent
119994b602
commit
aa9488755f
11
backend/.env.example
Normal file
11
backend/.env.example
Normal file
@ -0,0 +1,11 @@
|
||||
DATABASE_URL=postgres://username:password@db-host:5432/database_name
|
||||
DB_USER=
|
||||
DB_PASS=
|
||||
DB_HOST=
|
||||
DB_PORT=5432
|
||||
DB_NAME=
|
||||
PORT=5000
|
||||
JWT_SECRET=change-me
|
||||
ALLOWED_ORIGINS=http://localhost:3000
|
||||
SESSION_COOKIE_NAME=sid
|
||||
SESSION_TTL_DAYS=30
|
||||
@ -12,7 +12,10 @@ app.use(express.json());
|
||||
// Serve static files from public directory
|
||||
app.use('/test', express.static(path.join(__dirname, 'public')));
|
||||
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(",").map(origin => origin.trim());
|
||||
const allowedOrigins = (process.env.ALLOWED_ORIGINS || "")
|
||||
.split(",")
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean);
|
||||
app.use(
|
||||
cors({
|
||||
origin: function (origin, callback) {
|
||||
@ -20,10 +23,12 @@ app.use(
|
||||
if (allowedOrigins.includes(origin)) return callback(null, true);
|
||||
if (/^http:\/\/192\.168\.\d+\.\d+/.test(origin)) return callback(null, true);
|
||||
if (/^https:\/\/192\.168\.\d+\.\d+/.test(origin)) return callback(null, true);
|
||||
console.error(`🚫 CORS blocked origin: ${origin}`);
|
||||
console.error(`CORS blocked origin: ${origin}`);
|
||||
callback(new Error(`CORS blocked: ${origin}. Add this origin to ALLOWED_ORIGINS environment variable.`));
|
||||
},
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||
credentials: true,
|
||||
exposedHeaders: ["X-Request-Id"],
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
const { Pool } = require("pg");
|
||||
|
||||
const pool = new Pool({
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
host: process.env.DB_HOST,
|
||||
database: process.env.DB_NAME,
|
||||
port: 5432,
|
||||
});
|
||||
function buildPoolConfig() {
|
||||
if (process.env.DATABASE_URL) {
|
||||
return {
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
host: process.env.DB_HOST,
|
||||
database: process.env.DB_NAME,
|
||||
port: Number(process.env.DB_PORT || 5432),
|
||||
};
|
||||
}
|
||||
|
||||
const pool = new Pool(buildPoolConfig());
|
||||
|
||||
module.exports = pool;
|
||||
|
||||
@ -9,3 +9,8 @@ export const registerRequest = async (username, password, name) => {
|
||||
const res = await api.post("/auth/register", { username, password, name });
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const logoutRequest = async () => {
|
||||
const res = await api.post("/auth/logout");
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { API_BASE_URL } from "../config";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
@ -40,8 +41,16 @@ api.interceptors.response.use(
|
||||
payload.message = payload.error.message;
|
||||
}
|
||||
|
||||
if (error.response?.status === 401 &&
|
||||
normalizedMessage === "Invalid or expired token") {
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
window.location.pathname !== "/login" &&
|
||||
window.location.pathname !== "/register" &&
|
||||
[
|
||||
"Invalid or expired token",
|
||||
"Invalid or expired session",
|
||||
"Missing authentication",
|
||||
].includes(normalizedMessage)
|
||||
) {
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/login";
|
||||
alert("Your session has expired. Please log in again.");
|
||||
|
||||
@ -2,6 +2,7 @@ import "../../styles/components/Navbar.css";
|
||||
|
||||
import { useContext, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { logoutRequest } from "../../api/auth";
|
||||
import { AuthContext } from "../../context/AuthContext";
|
||||
import HouseholdSwitcher from "../household/HouseholdSwitcher";
|
||||
|
||||
@ -15,6 +16,18 @@ export default function Navbar() {
|
||||
setShowUserMenu(false);
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logoutRequest();
|
||||
} catch (_) {
|
||||
// Clear local auth state even if server logout fails.
|
||||
} finally {
|
||||
logout();
|
||||
closeMenus();
|
||||
window.location.href = "/login";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="navbar">
|
||||
{/* Left: Navigation Menu */}
|
||||
@ -72,7 +85,7 @@ export default function Navbar() {
|
||||
<span className="user-dropdown-username">{username}</span>
|
||||
<span className="user-dropdown-role">{role}</span>
|
||||
</div>
|
||||
<button className="user-dropdown-logout" onClick={() => { logout(); closeMenus(); }}>
|
||||
<button className="user-dropdown-logout" onClick={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,13 @@ export const AuthProvider = ({ children }) => {
|
||||
const [role, setRole] = useState(localStorage.getItem('role') || null);
|
||||
const [username, setUsername] = useState(localStorage.getItem('username') || null);
|
||||
|
||||
const clearAuthStorage = () => {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("userId");
|
||||
localStorage.removeItem("role");
|
||||
localStorage.removeItem("username");
|
||||
};
|
||||
|
||||
const login = (data) => {
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('userId', data.userId);
|
||||
@ -27,7 +34,7 @@ export const AuthProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.clear();
|
||||
clearAuthStorage();
|
||||
|
||||
setToken(null);
|
||||
setUserId(null);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user