Skip to main content

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

1

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>
2

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
3

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));
4

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 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)

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);
  }
});
When a token expires or becomes invalid:
  1. API returns 401 or 403 status
  2. apiFetch wrapper catches the error
  3. User is automatically logged out
  4. Local storage is cleared
  5. User is redirected to login page

Profile Menu

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

EndpointMethodPurposeResponse
/api/auth/loginPOSTAuthenticate user{ token: string, user: UserObject }
/api/cashoutsPOSTSubmit cashout (requires token){ success: boolean }
/api/cashoutsGETFetch 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

Only tokens are stored, never raw passwords. Tokens can be revoked server-side.
Check user roles before displaying sensitive UI elements or making privileged API calls.
Always implement automatic logout on 401/403 responses to prevent confused user states.
Check for valid token and user data on application initialization to prevent unauthorized access.

Build docs developers (and LLMs) love