Added code to validate if user authenticated then redirect to dashboard else login page. Added code to handle role based navebar tab displaying. Added code to handle access token or refresh token expiration and then redirect to login page.
This commit is contained in:
parent
ea7ac4cc72
commit
262307f17d
@ -106,7 +106,6 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
user_id INT NOT NULL,
|
user_id INT NOT NULL,
|
||||||
session_token VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
refresh_token VARCHAR(255) NOT NULL,
|
refresh_token VARCHAR(255) NOT NULL,
|
||||||
ip_address VARCHAR(45) NOT NULL,
|
ip_address VARCHAR(45) NOT NULL,
|
||||||
user_agent TEXT,
|
user_agent TEXT,
|
||||||
@ -115,7 +114,6 @@ CREATE TABLE IF NOT EXISTS user_sessions (
|
|||||||
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT fk_user_session_user FOREIGN KEY (user_id)
|
CONSTRAINT fk_user_session_user FOREIGN KEY (user_id)
|
||||||
REFERENCES users(id) ON DELETE CASCADE,
|
REFERENCES users(id) ON DELETE CASCADE,
|
||||||
CONSTRAINT unique_session_token UNIQUE(session_token),
|
|
||||||
CONSTRAINT unique_refresh_token UNIQUE(refresh_token)
|
CONSTRAINT unique_refresh_token UNIQUE(refresh_token)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-router-dom": "^6.14.1",
|
||||||
"router-dashboard": "file:"
|
"router-dashboard": "file:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,20 +1,35 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React from 'react';
|
||||||
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import Login from './components/Login';
|
import Login from './components/Login';
|
||||||
import DashboardLayout from './components/dashboard/DashboardLayout';
|
import DashboardLayout from './components/dashboard/DashboardLayout';
|
||||||
|
import { useAuth } from './contexts/AuthContext';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
// Simulating checking if the user is logged in (e.g., using localStorage or an API call)
|
|
||||||
useEffect(() => {
|
|
||||||
const userLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; // Example logic
|
|
||||||
setIsLoggedIn(userLoggedIn);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Routes>
|
||||||
{isLoggedIn ? <DashboardLayout /> : <Login />}
|
<Route
|
||||||
</>
|
path="/"
|
||||||
|
element={
|
||||||
|
isAuthenticated ? (
|
||||||
|
<DashboardLayout />
|
||||||
|
) : (
|
||||||
|
<Navigate to="/login" replace />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/login"
|
||||||
|
element={
|
||||||
|
isAuthenticated ? (
|
||||||
|
<Navigate to="/" replace />
|
||||||
|
) : (
|
||||||
|
<Login />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +1,83 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Header from './dashboard/Header';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
import { apiService } from '../services/api.service';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setIsAuthenticated } = useAuth(); // Access the setIsAuthenticated function
|
||||||
|
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [usernameError, setUsernameError] = useState('');
|
||||||
|
const [passwordError, setPasswordError] = useState('');
|
||||||
|
const [generalError, setGeneralError] = useState('');
|
||||||
|
|
||||||
const handleLogin = (event: React.FormEvent) => {
|
const [sessionExpiredMessage, setSessionExpiredMessage] = useState<string | null>(null); // State to store the session expired message
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if the URL has the sessionExpired query parameter
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const sessionExpired = urlParams.get('sessionExpired');
|
||||||
|
if (sessionExpired) {
|
||||||
|
setSessionExpiredMessage('Session expired or something went wrong. Please log in again.');
|
||||||
|
}
|
||||||
|
}, []); // Empty dependency array means this will run only on component mount
|
||||||
|
|
||||||
|
const handleLogin = async (event: React.FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Mock authentication logic
|
let hasError = false;
|
||||||
if (username === 'admin' && password === 'password') {
|
|
||||||
localStorage.setItem('user', JSON.stringify({ accessToken: 'fake-token' }));
|
// Validate username and password
|
||||||
window.location.href = '/dashboard';
|
if (!username.trim()) {
|
||||||
|
setUsernameError('Username is required');
|
||||||
|
hasError = true;
|
||||||
} else {
|
} else {
|
||||||
setError('Invalid username or password');
|
setUsernameError('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.trim()) {
|
||||||
|
setPasswordError('Password is required');
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
setPasswordError('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop execution if there are validation errors
|
||||||
|
if (hasError) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await apiService.login(username, password);
|
||||||
|
if (result) {
|
||||||
|
// Save tokens and user details in localStorage
|
||||||
|
localStorage.setItem('accessToken', result.accessToken);
|
||||||
|
localStorage.setItem('refreshToken', result.refreshToken);
|
||||||
|
localStorage.setItem('user', JSON.stringify(result.user));
|
||||||
|
localStorage.setItem('isLoggedIn', 'true');
|
||||||
|
|
||||||
|
// Update the authentication state in the context
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
|
||||||
|
// Redirect to Dashboard
|
||||||
|
//navigate('/');
|
||||||
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
setGeneralError('Invalid username or password');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setGeneralError(error.message || 'An unexpected error occurred');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-gray-100">
|
<div className="min-h-screen flex flex-col bg-gray-100">
|
||||||
{/* Header */}
|
|
||||||
<Header user={{}} onLogout={() => {}} />
|
|
||||||
|
|
||||||
{/* Login Content */}
|
{/* Login Content */}
|
||||||
<div className="flex flex-1 items-center justify-center">
|
<div className="flex flex-1 items-center justify-center">
|
||||||
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow">
|
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow">
|
||||||
<h2 className="text-2xl font-semibold text-center text-gray-700 mb-6">Login</h2>
|
<h2 className="text-2xl font-semibold text-center text-gray-700 mb-6">Login</h2>
|
||||||
<form onSubmit={handleLogin}>
|
<form onSubmit={handleLogin}>
|
||||||
|
{/* Username Field */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
||||||
Username
|
Username
|
||||||
@ -40,7 +90,10 @@ const Login: React.FC = () => {
|
|||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
||||||
placeholder="Enter your username"
|
placeholder="Enter your username"
|
||||||
/>
|
/>
|
||||||
|
{usernameError && <div className="text-red-500 text-sm mt-1">{usernameError}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Password Field */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
||||||
Password
|
Password
|
||||||
@ -53,10 +106,16 @@ const Login: React.FC = () => {
|
|||||||
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
className="mt-1 block w-full px-4 py-2 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500"
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
/>
|
/>
|
||||||
|
{passwordError && <div className="text-red-500 text-sm mt-1">{passwordError}</div>}
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
|
||||||
<div className="text-red-500 text-sm mb-4">{error}</div>
|
{/* General Error */}
|
||||||
)}
|
{generalError && <div className="text-red-500 text-sm mb-4">{generalError}</div>}
|
||||||
|
|
||||||
|
{/* Display the session expired message */}
|
||||||
|
{sessionExpiredMessage && <div className="text-red-500 text-sm mb-4">{sessionExpiredMessage}</div>}
|
||||||
|
|
||||||
|
{/* Login Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-150"
|
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-150"
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import RouterTable from './RouterTable';
|
|||||||
import RouterManagement from './pages/RouterManagement';
|
import RouterManagement from './pages/RouterManagement';
|
||||||
import { RouterData } from '../../types';
|
import { RouterData } from '../../types';
|
||||||
import { apiService } from '../../services/api.service';
|
import { apiService } from '../../services/api.service';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../../contexts/AuthContext'; // Import the useAuth hook
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
name: string;
|
name: string;
|
||||||
@ -15,16 +17,37 @@ interface User {
|
|||||||
type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
|
||||||
|
|
||||||
const DashboardLayout: React.FC = () => {
|
const DashboardLayout: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { isAuthenticated, setIsAuthenticated } = useAuth(); // Use AuthContext
|
||||||
const [activeTab, setActiveTab] = useState('dashboard');
|
const [activeTab, setActiveTab] = useState('dashboard');
|
||||||
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
||||||
const [routers, setRouters] = useState<RouterData[]>([]);
|
const [routers, setRouters] = useState<RouterData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [user] = useState<User>({
|
const [user, setUser] = useState<User | null>(null);
|
||||||
name: 'John Doe',
|
|
||||||
role: 'Administrator'
|
|
||||||
});
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
try {
|
||||||
|
const storedUser = localStorage.getItem('user');
|
||||||
|
if (storedUser) {
|
||||||
|
const { name, role }: { name: string; role: string } = JSON.parse(storedUser);
|
||||||
|
setUser({ name, role });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing user from localStorage:', error);
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchUser(); // Call the async function
|
||||||
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRouters = async () => {
|
const fetchRouters = async () => {
|
||||||
try {
|
try {
|
||||||
@ -64,9 +87,30 @@ const DashboardLayout: React.FC = () => {
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [activeFilter]);
|
}, [activeFilter]);
|
||||||
|
|
||||||
const handleLogout = () => {
|
// Handle logout functionality
|
||||||
|
const handleLogout = async () => {
|
||||||
console.log('Logging out...');
|
console.log('Logging out...');
|
||||||
// Add your logout logic here
|
try {
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken');
|
||||||
|
if (refreshToken) {
|
||||||
|
await apiService.logout(refreshToken); // Call logout API if available
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove only authentication-related items from localStorage
|
||||||
|
localStorage.removeItem('isLoggedIn');
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
|
||||||
|
setUser(null); // Set user state to null after logging out
|
||||||
|
setIsAuthenticated(false); // Update the authentication state in context
|
||||||
|
|
||||||
|
// Redirect to login page
|
||||||
|
//navigate('/login');
|
||||||
|
window.location.href = '/login';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during logout:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSummary = (routerData: RouterData[]) => {
|
const getSummary = (routerData: RouterData[]) => {
|
||||||
@ -218,7 +262,7 @@ const DashboardLayout: React.FC = () => {
|
|||||||
<div className="min-h-screen bg-gray-100">
|
<div className="min-h-screen bg-gray-100">
|
||||||
<Header user={user} onLogout={handleLogout} />
|
<Header user={user} onLogout={handleLogout} />
|
||||||
<div className="flex h-[calc(100vh-4rem)]">
|
<div className="flex h-[calc(100vh-4rem)]">
|
||||||
<Navbar activeTab={activeTab} onTabChange={setActiveTab} />
|
<Navbar activeTab={activeTab} onTabChange={setActiveTab} role={user?.role} />
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -9,11 +9,15 @@ interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
user: User;
|
user: User | null; // Allow user to be null
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
|
const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
|
||||||
|
if (!user) {
|
||||||
|
return null; // Don't render the header if the user is not available
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-white shadow-sm">
|
<header className="bg-white shadow-sm">
|
||||||
<div className="h-16 px-4 flex items-center justify-between">
|
<div className="h-16 px-4 flex items-center justify-between">
|
||||||
|
|||||||
@ -13,26 +13,29 @@ import {
|
|||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
onTabChange: (tab: string) => void;
|
onTabChange: (tab: string) => void;
|
||||||
|
role: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Tab {
|
interface Tab {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
|
roles: string[]; // Added roles to specify allowed roles
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange, role }) => {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
const [isPinned, setIsPinned] = useState(false);
|
const [isPinned, setIsPinned] = useState(false);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const TABS_WITH_PERMISSIONS: Tab[] = [
|
||||||
const tabs: Tab[] = [
|
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, roles: ['admin', 'operator', 'viewer'] },
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
{ id: 'routers', label: 'Router Management', icon: RouterIcon, roles: ['admin', 'operator'] },
|
||||||
{ id: 'routers', label: 'Router Management', icon: RouterIcon },
|
{ id: 'users', label: 'User Management', icon: Users, roles: ['admin'] },
|
||||||
{ id: 'users', label: 'User Management', icon: Users },
|
{ id: 'settings', label: 'Settings', icon: Settings, roles: ['admin'] },
|
||||||
{ id: 'settings', label: 'Settings', icon: Settings }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const filteredTabs = TABS_WITH_PERMISSIONS.filter((tab) => tab.roles.includes(role));
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (!isPinned) {
|
if (!isPinned) {
|
||||||
setIsCollapsed(false);
|
setIsCollapsed(false);
|
||||||
@ -71,7 +74,6 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
{/* Header with title and controls */}
|
|
||||||
<div className="p-4 border-b border-gray-700 flex items-center justify-between">
|
<div className="p-4 border-b border-gray-700 flex items-center justify-between">
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div>
|
<div>
|
||||||
@ -79,7 +81,11 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
<h2 className="text-sm font-semibold">System</h2>
|
<h2 className="text-sm font-semibold">System</h2>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`flex items-center gap-2 ${isCollapsed ? 'w-full justify-center' : 'justify-end'}`}>
|
<div
|
||||||
|
className={`flex items-center gap-2 ${
|
||||||
|
isCollapsed ? 'w-full justify-center' : 'justify-end'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{(isHovered || !isCollapsed) && (
|
{(isHovered || !isCollapsed) && (
|
||||||
<button
|
<button
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
@ -88,9 +94,11 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
}`}
|
}`}
|
||||||
title={isPinned ? 'Unpin sidebar' : 'Pin sidebar'}
|
title={isPinned ? 'Unpin sidebar' : 'Pin sidebar'}
|
||||||
>
|
>
|
||||||
<Pin
|
<Pin
|
||||||
size={16}
|
size={16}
|
||||||
className={`transform transition-transform ${isPinned ? 'rotate-45' : ''}`}
|
className={`transform transition-transform ${
|
||||||
|
isPinned ? 'rotate-45' : ''
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -99,18 +107,14 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
className="p-1 rounded hover:bg-gray-700 transition-colors"
|
className="p-1 rounded hover:bg-gray-700 transition-colors"
|
||||||
title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||||
>
|
>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
|
||||||
<ChevronRight size={16} />
|
|
||||||
) : (
|
|
||||||
<ChevronLeft size={16} />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation Items */}
|
{/* Navigation Items */}
|
||||||
<nav className="flex-1 p-2">
|
<nav className="flex-1 p-2">
|
||||||
{tabs.map(tab => {
|
{filteredTabs.map((tab) => {
|
||||||
const Icon = tab.icon;
|
const Icon = tab.icon;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -119,9 +123,11 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
className={`
|
className={`
|
||||||
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
||||||
transition-colors duration-200 group
|
transition-colors duration-200 group
|
||||||
${activeTab === tab.id
|
${
|
||||||
? 'bg-blue-600 text-white'
|
activeTab === tab.id
|
||||||
: 'text-gray-300 hover:bg-gray-800'}
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-gray-300 hover:bg-gray-800'
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
title={isCollapsed ? tab.label : undefined}
|
title={isCollapsed ? tab.label : undefined}
|
||||||
>
|
>
|
||||||
@ -131,41 +137,6 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Expanded overlay when collapsed and hovered */}
|
|
||||||
{isHovered && isCollapsed && !isPinned && (
|
|
||||||
<div
|
|
||||||
className="absolute left-16 top-0 bg-gray-900 text-white h-full w-64 shadow-lg z-50 overflow-hidden"
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<div className="p-4 border-b border-gray-700">
|
|
||||||
<h2 className="text-sm font-semibold">Router Management</h2>
|
|
||||||
<h2 className="text-sm font-semibold">System</h2>
|
|
||||||
</div>
|
|
||||||
<nav className="p-2">
|
|
||||||
{tabs.map(tab => {
|
|
||||||
const Icon = tab.icon;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={tab.id}
|
|
||||||
onClick={() => onTabChange(tab.id)}
|
|
||||||
className={`
|
|
||||||
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
|
|
||||||
transition-colors duration-200
|
|
||||||
${activeTab === tab.id
|
|
||||||
? 'bg-blue-600 text-white'
|
|
||||||
: 'text-gray-300 hover:bg-gray-800'}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<Icon size={20} />
|
|
||||||
<span>{tab.label}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
41
router-dashboard/src/contexts/AuthContext.tsx
Normal file
41
router-dashboard/src/contexts/AuthContext.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
setIsAuthenticated: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const useAuth = (): AuthContextType => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAuth must be used within an AuthProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthProvider: React.FC = ({ children }) => {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => {
|
||||||
|
// Check localStorage on initial load
|
||||||
|
return localStorage.getItem('isLoggedIn') === 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Sync authentication state with localStorage
|
||||||
|
if (isAuthenticated) {
|
||||||
|
localStorage.setItem('isLoggedIn', 'true');
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('isLoggedIn');
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
}
|
||||||
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,11 +1,17 @@
|
|||||||
// File: src/main.tsx
|
// File: src/main.tsx
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App.tsx'
|
import { BrowserRouter as Router } from 'react-router-dom'; // Import BrowserRouter for routing
|
||||||
import './index.css'
|
import App from './App.tsx'; // Ensure App is properly imported
|
||||||
|
import './index.css';
|
||||||
|
import { AuthProvider } from './contexts/AuthContext'; // Import the AuthProvider
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<AuthProvider> {/* Wrap the App with AuthProvider to enable authentication context */}
|
||||||
</React.StrictMode>,
|
<Router> {/* Wrap the App with Router to enable routing */}
|
||||||
)
|
<App />
|
||||||
|
</Router>
|
||||||
|
</AuthProvider>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|||||||
@ -11,6 +11,35 @@ const DEFAULT_OPTIONS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to get Authorization header
|
||||||
|
const getAuthHeaders = () => {
|
||||||
|
const accessToken = localStorage.getItem('accessToken');
|
||||||
|
if (!accessToken) {
|
||||||
|
console.error('No access token found in localStorage');
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to remove tokens from localStorage
|
||||||
|
const removeTokens = () => {
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
localStorage.removeItem('isLoggedIn');
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirectToLogin = (logMessage: string) => {
|
||||||
|
console.log(logMessage);
|
||||||
|
removeTokens(); // Remove all tokens if refresh token expired
|
||||||
|
|
||||||
|
// Redirect to login page with a query parameter indicating session expiry
|
||||||
|
window.location.href = '/login?sessionExpired=true'; // Redirect with the query parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper function to log API responses in development
|
// Helper function to log API responses in development
|
||||||
const logResponse = (prefix: string, data: any) => {
|
const logResponse = (prefix: string, data: any) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@ -19,9 +48,131 @@ const logResponse = (prefix: string, data: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
|
async login(username: string, password: string): Promise<{ accessToken: string; refreshToken: string; user: any } | null> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Invalid username or password');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
accessToken: data.accessToken,
|
||||||
|
refreshToken: data.refreshToken,
|
||||||
|
user: data.user,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh access token using the refresh token
|
||||||
|
async refreshToken(): Promise<boolean> {
|
||||||
|
const refreshToken = localStorage.getItem('refreshToken');
|
||||||
|
if (!refreshToken) {
|
||||||
|
console.error('No refresh token found in localStorage');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/auth/refresh-token`, {
|
||||||
|
method: 'POST',
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
body: JSON.stringify({ refreshToken }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log('Failed to refresh token: Expired or invalid token');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
// Save the new access and refresh tokens
|
||||||
|
localStorage.setItem('accessToken', data.accessToken);
|
||||||
|
localStorage.setItem('refreshToken', data.refreshToken);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error refreshing token:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API call with token handling for expired access token or refresh token
|
||||||
|
async fetchWithAuth(url: string, options: RequestInit = {}): Promise<Response> {
|
||||||
|
// Get the Authorization headers dynamically
|
||||||
|
const authOptions = getAuthHeaders();
|
||||||
|
|
||||||
|
// Merge the passed options with the Authorization header inside the headers object
|
||||||
|
const finalOptions = {
|
||||||
|
...options, // Include user-specified options like method, body, etc.
|
||||||
|
headers: {
|
||||||
|
...(options.headers || {}), // Include existing headers from the passed options
|
||||||
|
...authOptions, // Add Authorization header dynamically
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Print the request details before sending it
|
||||||
|
//console.log('Request Details:');
|
||||||
|
//console.log('URL:', url);
|
||||||
|
//console.log('Options:', JSON.stringify(finalOptions, null, 2)); // Pretty print the options
|
||||||
|
|
||||||
|
const response = await fetch(url, { ...finalOptions });
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
// Check for Access token expired
|
||||||
|
if (errorData.message === 'Access token expired') {
|
||||||
|
console.log("Access token is expired, initiating to re generate")
|
||||||
|
const refreshed = await this.refreshToken();
|
||||||
|
if (refreshed) {
|
||||||
|
// Get the Authorization headers dynamically
|
||||||
|
const authOptions = getAuthHeaders();
|
||||||
|
|
||||||
|
// Retry the original request with new access token
|
||||||
|
return fetch(url, { ...options, ...authOptions });
|
||||||
|
} else {
|
||||||
|
redirectToLogin("Refresh token is expired, removing all stored session data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Refresh token expired
|
||||||
|
if (errorData.message === 'Refresh token expired') {
|
||||||
|
redirectToLogin("Refresh token is expired, removing all stored session data");
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToLogin(errorData.message);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout(refreshToken: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/auth/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
body: JSON.stringify({ refreshToken }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getAllRouters(filter: FilterType = 'all'): Promise<RouterData[]> {
|
async getAllRouters(filter: FilterType = 'all'): Promise<RouterData[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/routers?filter=${filter}`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers?filter=${filter}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
@ -112,7 +263,7 @@ class ApiService {
|
|||||||
|
|
||||||
async getRouterById(id: number): Promise<RouterData | null> {
|
async getRouterById(id: number): Promise<RouterData | null> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -142,7 +293,7 @@ class ApiService {
|
|||||||
last_seen: new Date().toISOString(), // Set current timestamp
|
last_seen: new Date().toISOString(), // Set current timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/routers`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
body: JSON.stringify(backendData),
|
body: JSON.stringify(backendData),
|
||||||
@ -174,7 +325,7 @@ class ApiService {
|
|||||||
last_seen: new Date().toISOString() // Update timestamp on changes
|
last_seen: new Date().toISOString() // Update timestamp on changes
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
body: JSON.stringify(backendData),
|
body: JSON.stringify(backendData),
|
||||||
@ -193,7 +344,7 @@ class ApiService {
|
|||||||
|
|
||||||
async deleteRouter(id: number): Promise<boolean> {
|
async deleteRouter(id: number): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
@ -209,7 +360,7 @@ class ApiService {
|
|||||||
|
|
||||||
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await this.fetchWithAuth(
|
||||||
`${API_BASE_URL}/routers/facility/${encodeURIComponent(facility)}`,
|
`${API_BASE_URL}/routers/facility/${encodeURIComponent(facility)}`,
|
||||||
{ ...DEFAULT_OPTIONS }
|
{ ...DEFAULT_OPTIONS }
|
||||||
);
|
);
|
||||||
@ -226,7 +377,7 @@ class ApiService {
|
|||||||
|
|
||||||
async checkApiStatus(): Promise<boolean> {
|
async checkApiStatus(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/routers`, {
|
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
...DEFAULT_OPTIONS
|
...DEFAULT_OPTIONS
|
||||||
});
|
});
|
||||||
|
|||||||
21
router-dashboard/src/types/user.ts
Normal file
21
router-dashboard/src/types/user.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// User Role enum
|
||||||
|
export type UserRole = 'admin' | 'operator' | 'viewer' | 'api';
|
||||||
|
|
||||||
|
// User Status enum
|
||||||
|
export type UserStatus = 'active' | 'locked' | 'disabled';
|
||||||
|
|
||||||
|
// User Interface
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
password_hash: string;
|
||||||
|
role: UserRole;
|
||||||
|
status: UserStatus;
|
||||||
|
failed_login_attempts: number;
|
||||||
|
last_login: Date | null;
|
||||||
|
password_changed_at: Date;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
}
|
||||||
@ -19,7 +19,7 @@ export class AuthController {
|
|||||||
|
|
||||||
const user = await this.service.validateUser(username, password);
|
const user = await this.service.validateUser(username, password);
|
||||||
|
|
||||||
const { accessToken, refreshToken, sessionToken } = this.service.generateTokens(user);
|
const { accessToken, refreshToken} = this.service.generateTokens(user);
|
||||||
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
||||||
|
|
||||||
// Check for an active session for this user
|
// Check for an active session for this user
|
||||||
@ -48,7 +48,6 @@ export class AuthController {
|
|||||||
logger.info('Updating expired session.');
|
logger.info('Updating expired session.');
|
||||||
await this.service.updateUserSession(existingSession.refresh_token, {
|
await this.service.updateUserSession(existingSession.refresh_token, {
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
session_token: sessionToken,
|
|
||||||
expires_at: expiresAt
|
expires_at: expiresAt
|
||||||
});
|
});
|
||||||
res.json({
|
res.json({
|
||||||
@ -69,7 +68,6 @@ export class AuthController {
|
|||||||
// No session matches, Create a new user sessions
|
// No session matches, Create a new user sessions
|
||||||
const userSessionDTO: Partial<CreateUserSessionDTO> = {
|
const userSessionDTO: Partial<CreateUserSessionDTO> = {
|
||||||
user_id : user.id,
|
user_id : user.id,
|
||||||
session_token : sessionToken,
|
|
||||||
refresh_token : refreshToken,
|
refresh_token : refreshToken,
|
||||||
ip_address : req.ip,
|
ip_address : req.ip,
|
||||||
user_agent : req.headers['user-agent'],
|
user_agent : req.headers['user-agent'],
|
||||||
@ -135,12 +133,11 @@ export class AuthController {
|
|||||||
role: userData.role as UserRole,
|
role: userData.role as UserRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { accessToken, refreshToken: newRefreshToken, sessionToken: newSessionToken } =
|
const { accessToken, refreshToken: newRefreshToken} =
|
||||||
this.service.generateTokens(user);
|
this.service.generateTokens(user);
|
||||||
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
|
||||||
|
|
||||||
const userSessionData = {
|
const userSessionData = {
|
||||||
session_token: newSessionToken,
|
|
||||||
refresh_token: newRefreshToken,
|
refresh_token: newRefreshToken,
|
||||||
expires_at: newExpiresAt
|
expires_at: newExpiresAt
|
||||||
}
|
}
|
||||||
@ -176,7 +173,7 @@ export class AuthController {
|
|||||||
return res.status(400).json({ message: "Refresh Token is required" });
|
return res.status(400).json({ message: "Refresh Token is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ message: 'Logged out successfully' });
|
res.status(200).json({ message: 'Logged out successfully' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
logger.error('Logout error:', { message: error.message, stack: error.stack });
|
logger.error('Logout error:', { message: error.message, stack: error.stack });
|
||||||
|
|||||||
@ -141,7 +141,6 @@ export class RouterRepository {
|
|||||||
[routerId]
|
[routerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Containers for router ${routerId}:`, rows);
|
|
||||||
return rows as Container[];
|
return rows as Container[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error fetching Containers for router ${routerId}:`, error);
|
logger.error(`Error fetching Containers for router ${routerId}:`, error);
|
||||||
|
|||||||
@ -144,12 +144,11 @@ export class UserRepository {
|
|||||||
|
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
`INSERT INTO user_sessions (
|
`INSERT INTO user_sessions (
|
||||||
user_id, session_token, refresh_token, ip_address,
|
user_id, refresh_token, ip_address,
|
||||||
user_agent, expires_at, created_at, last_activity
|
user_agent, expires_at, created_at, last_activity
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
) VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||||
[
|
[
|
||||||
userSession.user_id,
|
userSession.user_id,
|
||||||
userSession.session_token,
|
|
||||||
userSession.refresh_token,
|
userSession.refresh_token,
|
||||||
userSession.ip_address,
|
userSession.ip_address,
|
||||||
userSession.user_agent,
|
userSession.user_agent,
|
||||||
|
|||||||
@ -19,7 +19,8 @@ export class AuthService {
|
|||||||
return jwt.sign(
|
return jwt.sign(
|
||||||
{ userId: user.id, username: user.username, role: user.role },
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
process.env.JWT_SECRET as string,
|
process.env.JWT_SECRET as string,
|
||||||
{ expiresIn: '15m' }
|
//{ expiresIn: '30m' }
|
||||||
|
{ expiresIn: '1m' }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,18 +29,18 @@ export class AuthService {
|
|||||||
const accessToken = jwt.sign(
|
const accessToken = jwt.sign(
|
||||||
{ userId: user.id, username: user.username, role: user.role },
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
process.env.JWT_SECRET as string,
|
process.env.JWT_SECRET as string,
|
||||||
{ expiresIn: '15m' }
|
//{ expiresIn: '30m' }
|
||||||
|
{ expiresIn: '1m' }
|
||||||
);
|
);
|
||||||
|
|
||||||
const refreshToken = jwt.sign(
|
const refreshToken = jwt.sign(
|
||||||
{ userId: user.id, username: user.username, role: user.role, type: 'refresh' }, // Include a claim to distinguish token types
|
{ userId: user.id, username: user.username, role: user.role, type: 'refresh' }, // Include a claim to distinguish token types
|
||||||
process.env.JWT_SECRET as string,
|
process.env.JWT_SECRET as string,
|
||||||
{ expiresIn: '7d' } // Longer expiry for refresh token
|
{ expiresIn: '7d' } // Longer expiry for refresh token
|
||||||
|
//{ expiresIn: '1m' }
|
||||||
);
|
);
|
||||||
|
|
||||||
const sessionToken = crypto.randomBytes(40).toString('hex');
|
return { accessToken, refreshToken };
|
||||||
|
|
||||||
return { accessToken, refreshToken, sessionToken };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the user by username and password
|
// Validate the user by username and password
|
||||||
@ -64,7 +65,6 @@ export class AuthService {
|
|||||||
async createUserSession (userSessionData: Partial<UserSession>) {
|
async createUserSession (userSessionData: Partial<UserSession>) {
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
'user_id',
|
'user_id',
|
||||||
'session_token',
|
|
||||||
'refresh_token',
|
'refresh_token',
|
||||||
'ip_address',
|
'ip_address',
|
||||||
'user_agent',
|
'user_agent',
|
||||||
@ -103,7 +103,6 @@ export class AuthService {
|
|||||||
|
|
||||||
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
|
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
'session_token',
|
|
||||||
'refresh_token',
|
'refresh_token',
|
||||||
'expires_at'
|
'expires_at'
|
||||||
];
|
];
|
||||||
|
|||||||
@ -16,9 +16,9 @@ export class SetupService {
|
|||||||
const defaultUsers = [
|
const defaultUsers = [
|
||||||
{ name: 'API User', username: 'api_user', email: 'apiuser@ve.com', password: 'api_user@@124', role: 'api' },
|
{ name: 'API User', username: 'api_user', email: 'apiuser@ve.com', password: 'api_user@@124', role: 'api' },
|
||||||
{ name: 'Administrator', username: 'admin', email: 'admin@ve.com', password: 'admin@@007', role: 'admin' },
|
{ name: 'Administrator', username: 'admin', email: 'admin@ve.com', password: 'admin@@007', role: 'admin' },
|
||||||
|
{ name: 'Maqbool Patel', username: 'maqbool', email: 'maqbool@ve.com', password: 'maqbool@@210', role: 'admin' },
|
||||||
{ name: 'Kavya Raghunath', username: 'kavya', email: 'kavya@ve.com', password: 'kavya@@124', role: 'viewer' },
|
{ name: 'Kavya Raghunath', username: 'kavya', email: 'kavya@ve.com', password: 'kavya@@124', role: 'viewer' },
|
||||||
{ name: 'Reid McKenzie', username: 'reid', email: 'reid@ve.com', password: 'reid@@321', role: 'viewer' },
|
{ name: 'Reid McKenzie', username: 'reid', email: 'reid@ve.com', password: 'reid@@321', role: 'viewer' }
|
||||||
{ name: 'Maqbool Patel', username: 'maqbool', email: 'maqbool@ve.com', password: 'maqbool@@210', role: 'viewer' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const createdUsers = [];
|
const createdUsers = [];
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export interface UpdateUser {
|
|||||||
export interface UserSession {
|
export interface UserSession {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
session_token: string;
|
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
ip_address: string;
|
ip_address: string;
|
||||||
user_agent: string | null;
|
user_agent: string | null;
|
||||||
@ -50,7 +49,6 @@ export interface UserSession {
|
|||||||
// Create User Session Interface
|
// Create User Session Interface
|
||||||
export interface CreateUserSessionDTO {
|
export interface CreateUserSessionDTO {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
session_token: string;
|
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
ip_address: string;
|
ip_address: string;
|
||||||
user_agent: string | null;
|
user_agent: string | null;
|
||||||
@ -75,7 +73,6 @@ export interface UserWithSession {
|
|||||||
};
|
};
|
||||||
session: {
|
session: {
|
||||||
id: number;
|
id: number;
|
||||||
session_token: string;
|
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
ip_address: string;
|
ip_address: string;
|
||||||
user_agent: string | null;
|
user_agent: string | null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user