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:
shankar 2024-12-09 17:06:01 +05:30
parent ea7ac4cc72
commit 262307f17d
17 changed files with 432 additions and 130 deletions

View File

@ -106,7 +106,6 @@ CREATE TABLE IF NOT EXISTS users (
CREATE TABLE IF NOT EXISTS user_sessions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
session_token VARCHAR(255) UNIQUE NOT NULL,
refresh_token VARCHAR(255) NOT NULL,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT,
@ -115,7 +114,6 @@ CREATE TABLE IF NOT EXISTS user_sessions (
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_user_session_user FOREIGN KEY (user_id)
REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT unique_session_token UNIQUE(session_token),
CONSTRAINT unique_refresh_token UNIQUE(refresh_token)
);

View File

@ -13,6 +13,7 @@
"lucide-react": "^0.294.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.1",
"router-dashboard": "file:"
},
"devDependencies": {

View File

@ -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 DashboardLayout from './components/dashboard/DashboardLayout';
import { useAuth } from './contexts/AuthContext';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
// 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);
}, []);
const { isAuthenticated } = useAuth();
return (
<>
{isLoggedIn ? <DashboardLayout /> : <Login />}
</>
<Routes>
<Route
path="/"
element={
isAuthenticated ? (
<DashboardLayout />
) : (
<Navigate to="/login" replace />
)
}
/>
<Route
path="/login"
element={
isAuthenticated ? (
<Navigate to="/" replace />
) : (
<Login />
)
}
/>
</Routes>
);
}

View File

@ -1,33 +1,83 @@
import React, { useState } from 'react';
import Header from './dashboard/Header';
import React, { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { apiService } from '../services/api.service';
import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => {
const navigate = useNavigate();
const { setIsAuthenticated } = useAuth(); // Access the setIsAuthenticated function
const [username, setUsername] = 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();
// Mock authentication logic
if (username === 'admin' && password === 'password') {
localStorage.setItem('user', JSON.stringify({ accessToken: 'fake-token' }));
window.location.href = '/dashboard';
let hasError = false;
// Validate username and password
if (!username.trim()) {
setUsernameError('Username is required');
hasError = true;
} 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 (
<div className="min-h-screen flex flex-col bg-gray-100">
{/* Header */}
<Header user={{}} onLogout={() => {}} />
{/* Login Content */}
<div className="flex flex-1 items-center justify-center">
<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>
<form onSubmit={handleLogin}>
{/* Username Field */}
<div className="mb-4">
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
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"
placeholder="Enter your username"
/>
{usernameError && <div className="text-red-500 text-sm mt-1">{usernameError}</div>}
</div>
{/* Password Field */}
<div className="mb-6">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
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"
placeholder="Enter your password"
/>
{passwordError && <div className="text-red-500 text-sm mt-1">{passwordError}</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
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition duration-150"

View File

@ -6,6 +6,8 @@ import RouterTable from './RouterTable';
import RouterManagement from './pages/RouterManagement';
import { RouterData } from '../../types';
import { apiService } from '../../services/api.service';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext'; // Import the useAuth hook
interface User {
name: string;
@ -15,15 +17,36 @@ interface User {
type FilterType = 'all' | 'active' | 'critical' | 'diskAlert';
const DashboardLayout: React.FC = () => {
const navigate = useNavigate();
const { isAuthenticated, setIsAuthenticated } = useAuth(); // Use AuthContext
const [activeTab, setActiveTab] = useState('dashboard');
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
const [routers, setRouters] = useState<RouterData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [user] = useState<User>({
name: 'John Doe',
role: 'Administrator'
});
const [user, setUser] = useState<User | null>(null);
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(() => {
const fetchRouters = async () => {
@ -64,9 +87,30 @@ const DashboardLayout: React.FC = () => {
return () => clearInterval(interval);
}, [activeFilter]);
const handleLogout = () => {
// Handle logout functionality
const handleLogout = async () => {
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[]) => {
@ -218,7 +262,7 @@ const DashboardLayout: React.FC = () => {
<div className="min-h-screen bg-gray-100">
<Header user={user} onLogout={handleLogout} />
<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">
{renderContent()}
</main>

View File

@ -9,11 +9,15 @@ interface User {
}
interface HeaderProps {
user: User;
user: User | null; // Allow user to be null
onLogout: () => void;
}
const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
if (!user) {
return null; // Don't render the header if the user is not available
}
return (
<header className="bg-white shadow-sm">
<div className="h-16 px-4 flex items-center justify-between">

View File

@ -13,26 +13,29 @@ import {
interface NavbarProps {
activeTab: string;
onTabChange: (tab: string) => void;
role: string;
}
interface Tab {
id: string;
label: string;
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 [isPinned, setIsPinned] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const tabs: Tab[] = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ id: 'routers', label: 'Router Management', icon: RouterIcon },
{ id: 'users', label: 'User Management', icon: Users },
{ id: 'settings', label: 'Settings', icon: Settings }
const TABS_WITH_PERMISSIONS: Tab[] = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, roles: ['admin', 'operator', 'viewer'] },
{ id: 'routers', label: 'Router Management', icon: RouterIcon, roles: ['admin', 'operator'] },
{ id: 'users', label: 'User Management', icon: Users, roles: ['admin'] },
{ id: 'settings', label: 'Settings', icon: Settings, roles: ['admin'] },
];
const filteredTabs = TABS_WITH_PERMISSIONS.filter((tab) => tab.roles.includes(role));
const handleMouseEnter = () => {
if (!isPinned) {
setIsCollapsed(false);
@ -71,7 +74,6 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* Header with title and controls */}
<div className="p-4 border-b border-gray-700 flex items-center justify-between">
{!isCollapsed && (
<div>
@ -79,7 +81,11 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
<h2 className="text-sm font-semibold">System</h2>
</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) && (
<button
onClick={togglePin}
@ -90,7 +96,9 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
>
<Pin
size={16}
className={`transform transition-transform ${isPinned ? 'rotate-45' : ''}`}
className={`transform transition-transform ${
isPinned ? 'rotate-45' : ''
}`}
/>
</button>
)}
@ -99,18 +107,14 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
className="p-1 rounded hover:bg-gray-700 transition-colors"
title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{isCollapsed ? (
<ChevronRight size={16} />
) : (
<ChevronLeft size={16} />
)}
{isCollapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
</button>
</div>
</div>
{/* Navigation Items */}
<nav className="flex-1 p-2">
{tabs.map(tab => {
{filteredTabs.map((tab) => {
const Icon = tab.icon;
return (
<button
@ -119,9 +123,11 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
className={`
w-full flex items-center gap-3 px-3 py-2 mb-1 rounded-lg
transition-colors duration-200 group
${activeTab === tab.id
${
activeTab === tab.id
? 'bg-blue-600 text-white'
: 'text-gray-300 hover:bg-gray-800'}
: 'text-gray-300 hover:bg-gray-800'
}
`}
title={isCollapsed ? tab.label : undefined}
>
@ -131,41 +137,6 @@ const Navbar: React.FC<NavbarProps> = ({ activeTab, onTabChange }) => {
);
})}
</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>
);
};

View 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>
);
};

View File

@ -1,11 +1,17 @@
// File: src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom'; // Import BrowserRouter for routing
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(
<React.StrictMode>
<AuthProvider> {/* Wrap the App with AuthProvider to enable authentication context */}
<Router> {/* Wrap the App with Router to enable routing */}
<App />
</React.StrictMode>,
)
</Router>
</AuthProvider>
</React.StrictMode>
);

View File

@ -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
const logResponse = (prefix: string, data: any) => {
if (process.env.NODE_ENV === 'development') {
@ -19,9 +48,131 @@ const logResponse = (prefix: string, data: any) => {
};
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[]> {
try {
const response = await fetch(`${API_BASE_URL}/routers?filter=${filter}`, {
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers?filter=${filter}`, {
method: 'GET',
...DEFAULT_OPTIONS
});
@ -112,7 +263,7 @@ class ApiService {
async getRouterById(id: number): Promise<RouterData | null> {
try {
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
...DEFAULT_OPTIONS
});
if (!response.ok) {
@ -142,7 +293,7 @@ class ApiService {
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',
...DEFAULT_OPTIONS,
body: JSON.stringify(backendData),
@ -174,7 +325,7 @@ class ApiService {
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',
...DEFAULT_OPTIONS,
body: JSON.stringify(backendData),
@ -193,7 +344,7 @@ class ApiService {
async deleteRouter(id: number): Promise<boolean> {
try {
const response = await fetch(`${API_BASE_URL}/routers/${id}`, {
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers/${id}`, {
method: 'DELETE',
...DEFAULT_OPTIONS
});
@ -209,7 +360,7 @@ class ApiService {
async getRoutersByFacility(facility: string): Promise<RouterData[]> {
try {
const response = await fetch(
const response = await this.fetchWithAuth(
`${API_BASE_URL}/routers/facility/${encodeURIComponent(facility)}`,
{ ...DEFAULT_OPTIONS }
);
@ -226,7 +377,7 @@ class ApiService {
async checkApiStatus(): Promise<boolean> {
try {
const response = await fetch(`${API_BASE_URL}/routers`, {
const response = await this.fetchWithAuth(`${API_BASE_URL}/routers`, {
method: 'GET',
...DEFAULT_OPTIONS
});

View 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;
}

View File

@ -19,7 +19,7 @@ export class AuthController {
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
// Check for an active session for this user
@ -48,7 +48,6 @@ export class AuthController {
logger.info('Updating expired session.');
await this.service.updateUserSession(existingSession.refresh_token, {
refresh_token: refreshToken,
session_token: sessionToken,
expires_at: expiresAt
});
res.json({
@ -69,7 +68,6 @@ export class AuthController {
// No session matches, Create a new user sessions
const userSessionDTO: Partial<CreateUserSessionDTO> = {
user_id : user.id,
session_token : sessionToken,
refresh_token : refreshToken,
ip_address : req.ip,
user_agent : req.headers['user-agent'],
@ -135,12 +133,11 @@ export class AuthController {
role: userData.role as UserRole,
};
const { accessToken, refreshToken: newRefreshToken, sessionToken: newSessionToken } =
const { accessToken, refreshToken: newRefreshToken} =
this.service.generateTokens(user);
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days expiration
const userSessionData = {
session_token: newSessionToken,
refresh_token: newRefreshToken,
expires_at: newExpiresAt
}
@ -176,7 +173,7 @@ export class AuthController {
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) {
const error = err as Error;
logger.error('Logout error:', { message: error.message, stack: error.stack });

View File

@ -141,7 +141,6 @@ export class RouterRepository {
[routerId]
);
logger.info(`Containers for router ${routerId}:`, rows);
return rows as Container[];
} catch (error) {
logger.error(`Error fetching Containers for router ${routerId}:`, error);

View File

@ -144,12 +144,11 @@ export class UserRepository {
const [result] = await pool.query(
`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
) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())`,
) VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
[
userSession.user_id,
userSession.session_token,
userSession.refresh_token,
userSession.ip_address,
userSession.user_agent,

View File

@ -19,7 +19,8 @@ export class AuthService {
return jwt.sign(
{ userId: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET as string,
{ expiresIn: '15m' }
//{ expiresIn: '30m' }
{ expiresIn: '1m' }
);
};
@ -28,18 +29,18 @@ export class AuthService {
const accessToken = jwt.sign(
{ userId: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET as string,
{ expiresIn: '15m' }
//{ expiresIn: '30m' }
{ expiresIn: '1m' }
);
const refreshToken = jwt.sign(
{ userId: user.id, username: user.username, role: user.role, type: 'refresh' }, // Include a claim to distinguish token types
process.env.JWT_SECRET as string,
{ expiresIn: '7d' } // Longer expiry for refresh token
//{ expiresIn: '1m' }
);
const sessionToken = crypto.randomBytes(40).toString('hex');
return { accessToken, refreshToken, sessionToken };
return { accessToken, refreshToken };
}
// Validate the user by username and password
@ -64,7 +65,6 @@ export class AuthService {
async createUserSession (userSessionData: Partial<UserSession>) {
const requiredFields = [
'user_id',
'session_token',
'refresh_token',
'ip_address',
'user_agent',
@ -103,7 +103,6 @@ export class AuthService {
async updateUserSession (refreshToken:string, userSessionData: Partial<UserSession>) {
const requiredFields = [
'session_token',
'refresh_token',
'expires_at'
];

View File

@ -16,9 +16,9 @@ export class SetupService {
const defaultUsers = [
{ 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: '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: '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' },
{ name: 'Reid McKenzie', username: 'reid', email: 'reid@ve.com', password: 'reid@@321', role: 'viewer' }
];
const createdUsers = [];

View File

@ -38,7 +38,6 @@ export interface UpdateUser {
export interface UserSession {
id: number;
user_id: number;
session_token: string;
refresh_token: string;
ip_address: string;
user_agent: string | null;
@ -50,7 +49,6 @@ export interface UserSession {
// Create User Session Interface
export interface CreateUserSessionDTO {
user_id: number;
session_token: string;
refresh_token: string;
ip_address: string;
user_agent: string | null;
@ -75,7 +73,6 @@ export interface UserWithSession {
};
session: {
id: number;
session_token: string;
refresh_token: string;
ip_address: string;
user_agent: string | null;