Overview
OPS Workspace implements a secure JWT-based authentication system with role-based access control. The system validates users against a centralized backend and manages sessions across all applications.
Authentication Flow
User Login
Users enter their credentials on the login page at / (index.html:94-104) < form id = "loginForm" >
< input type = "text" id = "username" placeholder = "Usuario" required >
< input type = "password" id = "password" placeholder = "Contraseña" required >
< button type = "submit" class = "btn-primary" > Ingresar </ button >
</ form >
API Validation
Credentials are sent to the authentication endpoint: const res = await fetch ( ` ${ API_BASE } /auth/login` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
username: document . getElementById ( 'username' ). value . trim (),
password: document . getElementById ( 'password' ). value
})
});
API Endpoint: https://general-cashouts.onrender.com/api/auth/login
Token Storage
On successful authentication, the JWT token and user data are stored locally: localStorage . setItem ( 'token' , data . token );
localStorage . setItem ( 'user' , JSON . stringify ( data . user ));
Dashboard Access
The dashboard displays available applications based on user role (index.html:106-145)
User Roles
OPS Workspace supports three distinct user roles:
Supervisor
Analista
Chats
Supervisor Role Badge Color: Purple (rgba(94, 106, 210, 0.15))Permissions:
Full access to all applications
Can manage team members
Administrative privileges in Operapedia
Access to cashout review queue
View all statistics and leaderboards
UI Indicators: roleBadge . style . background = 'rgba(94, 106, 210, 0.15)' ;
roleBadge . style . color = '#6f7bf0' ;
roleBadge . style . border = '1px solid rgba(94, 106, 210, 0.3)' ;
Special Features:
“Manage Team” button visible (index.html:54-56)
Edit mode enabled in Operapedia (operapedia/index.html:222-229)
Analista Role Badge Color: Green (rgba(46, 204, 113, 0.15))Permissions:
Access to both Cashouts and Operapedia
Can submit cashout requests
Can review cashouts
Read-only access in Operapedia
View personal statistics
UI Indicators: roleBadge . style . background = 'rgba(46, 204, 113, 0.15)' ;
roleBadge . style . color = '#2ecc71' ;
roleBadge . style . border = '1px solid rgba(46, 204, 113, 0.3)' ;
Chats Role Badge Color: Orange (rgba(243, 156, 18, 0.15))Permissions:
Access to Operapedia ONLY
Read-only access to company credentials
Cannot access Cashouts system
UI Indicators: roleBadge . style . background = 'rgba(243, 156, 18, 0.15)' ;
roleBadge . style . color = '#f39c12' ;
roleBadge . style . border = '1px solid rgba(243, 156, 18, 0.3)' ;
Restrictions: // Cashouts card hidden for Chats role
if ( user . role === 'chats' ) {
cardCashouts . style . display = 'none' ;
}
JWT Token Management
Token Inclusion
Every API request automatically includes the JWT token in the Authorization header:
async function apiFetch ( endpoint , options = {}) {
const headers = {
'Content-Type' : 'application/json' ,
... options . headers
};
if ( token ) headers [ 'Authorization' ] = `Bearer ${ token } ` ;
const response = await fetch ( ` ${ API_BASE }${ endpoint } ` , {
... options ,
headers
});
// Auto-logout on unauthorized
if ( response . status === 401 || response . status === 403 ) {
cerrarSesion ();
throw new Error ( "Sesión expirada o acceso denegado" );
}
return response ;
}
Token Validation
On page load, the system checks for existing sessions: document . addEventListener ( 'DOMContentLoaded' , () => {
const token = localStorage . getItem ( 'token' );
const userStr = localStorage . getItem ( 'user' );
if ( token && userStr ) {
const user = JSON . parse ( userStr );
mostrarDashboard ( user );
}
});
Automatic Expiration Handling
When a token expires or becomes invalid:
API returns 401 or 403 status
apiFetch wrapper catches the error
User is automatically logged out
Local storage is cleared
User is redirected to login page
Authenticated users have access to a profile dropdown showing their information:
function mostrarDashboard ( user ) {
// Display first name in trigger
const primerNombre = user . fullName . split ( ' ' )[ 0 ];
document . getElementById ( 'triggerName' ). textContent = primerNombre ;
// Populate dropdown menu
document . getElementById ( 'profileFullName' ). textContent = user . fullName ;
document . getElementById ( 'profileUsername' ). textContent = '@' + user . username ;
document . getElementById ( 'profileRole' ). textContent = user . role . toUpperCase ();
}
Profile Menu Components (index.html:108-127):
Full name display
Username with @ prefix
Role badge with color coding
Logout button
Security Features
Read-Only Operator Name In the Cashouts system, the operator name field is auto-filled and locked: const operatorNameInput = document . getElementById ( 'operatorName' );
if ( operatorNameInput ) {
operatorNameInput . value = currentUser . fullName ;
operatorNameInput . readOnly = true ; // Prevents impersonation
}
Role-Based UI UI elements dynamically hide based on user role: if ( currentUser . role === 'supervisor' ) {
manageTeamBtn . style . display = 'block' ;
} else {
manageTeamBtn . style . display = 'none' ;
}
Token Auto-Refresh Tokens are validated on every API call. Invalid tokens trigger automatic logout to prevent unauthorized access.
Secure Password Modal Admin actions in Operapedia require password re-authentication: const ADMIN_PASSWORD = 'superctrl2023' ;
const pwd = await showPasswordModal (
'Acceso de administrador' ,
'Introduce la contraseña para gestionar el catálogo.'
);
if ( pwd !== ADMIN_PASSWORD ) {
toast . textContent = '❌ Contraseña incorrecta' ;
return ;
}
Logout Process
function logout () {
// Clear stored credentials
localStorage . removeItem ( 'token' );
localStorage . removeItem ( 'user' );
// Reload to show login screen
location . reload ();
}
Logout triggers:
Manual logout button click
Token expiration (401/403 response)
Session timeout
Cross-Application Authentication
All applications (Cashouts, Operapedia) verify authentication on load:
Operapedia Integration
// operapedia/index.html:209-230
document . addEventListener ( "DOMContentLoaded" , () => {
const token = localStorage . getItem ( 'token' );
const userStr = localStorage . getItem ( 'user' );
if ( ! token || ! userStr ) {
window . location . href = '/' ; // Redirect to Hub
return ;
}
const user = JSON . parse ( userStr );
// Auto-grant admin privileges to supervisors
if ( user . role === 'supervisor' ) {
localStorage . setItem ( 'credentialsAdminLoggedIn' , 'true' );
} else {
localStorage . removeItem ( 'credentialsAdminLoggedIn' );
// Hide edit buttons for non-supervisors
document . getElementById ( 'editModeBtn' ). style . display = 'none' ;
document . getElementById ( 'catalogBtn' ). style . display = 'none' ;
}
});
Cashouts Integration
// cashouts/index.html:597-623
function iniciarApp ( jwtToken , userData ) {
token = jwtToken ;
currentUser = userData ;
showNotification ( `¡Bienvenido, ${ currentUser . fullName } !` , 'success' );
// Auto-fill operator name (locked)
const operatorNameInput = document . getElementById ( 'operatorName' );
if ( operatorNameInput ) {
operatorNameInput . value = currentUser . fullName ;
operatorNameInput . readOnly = true ;
}
// Enable supervisor features
if ( currentUser . role === 'supervisor' ) {
manageTeamBtn . style . display = 'block' ;
}
}
API Reference
Endpoint Method Purpose Response /api/auth/loginPOST Authenticate user { token: string, user: UserObject }/api/cashoutsPOST Submit cashout (requires token) { success: boolean }/api/cashoutsGET Fetch cashouts (requires token) CashoutItem[]
Base URL: https://general-cashouts.onrender.com/api
All authenticated endpoints automatically validate the JWT token via the apiFetch wrapper. Requests without valid tokens receive 401/403 responses.
Best Practices
Never Store Passwords Locally
Only tokens are stored, never raw passwords. Tokens can be revoked server-side.
Use Role-Based Access Control
Check user roles before displaying sensitive UI elements or making privileged API calls.
Handle Token Expiration Gracefully
Always implement automatic logout on 401/403 responses to prevent confused user states.
Validate on Every Page Load
Check for valid token and user data on application initialization to prevent unauthorized access.