From a37e60eb2af81bd8cc3dad550717bef9f1cbc94d Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Tue, 30 Jun 2026 16:57:11 +0000 Subject: [PATCH] fix(auth): restringe workshop/mecanico solo por servidor y cookie - Elimina el script inline que redirigia a todos los usuarios. - auth_bp.py: la respuesta de login ahora setea cookie pos_role. - app.py: before_request redirige a /pos/workshop solo cuando la cookie indica rol workshop/mechanic (no depende de JS cacheado). - login.js/login.min.js/app-init.js: sincronizan/limpian la cookie pos_role junto con el token en login y logout. Tests: 35 passed --- pos/app.py | 14 +++++++++++++- pos/blueprints/auth_bp.py | 12 +++++++++--- pos/static/js/app-init.js | 1 + pos/static/js/login.js | 1 + pos/static/js/login.min.js | 2 +- pos/templates/accounting.html | 15 +-------------- pos/templates/catalog.html | 15 +-------------- pos/templates/config.html | 15 +-------------- pos/templates/customers.html | 15 +-------------- pos/templates/dashboard.html | 15 +-------------- pos/templates/diagrams.html | 15 +-------------- pos/templates/fleet.html | 15 +-------------- pos/templates/historical_sales.html | 15 +-------------- pos/templates/inventory.html | 15 +-------------- pos/templates/invoicing.html | 15 +-------------- pos/templates/marketplace.html | 15 +-------------- pos/templates/marketplace_external.html | 15 +-------------- pos/templates/pos.html | 15 +-------------- pos/templates/quotations.html | 15 +-------------- pos/templates/reports.html | 15 +-------------- pos/templates/supplier_catalog.html | 15 +-------------- pos/templates/whatsapp.html | 15 +-------------- 22 files changed, 42 insertions(+), 243 deletions(-) diff --git a/pos/app.py b/pos/app.py index 46961dd..3961b94 100644 --- a/pos/app.py +++ b/pos/app.py @@ -1,4 +1,4 @@ -from flask import Flask +from flask import Flask, request, redirect from json_provider import OrjsonProvider @@ -10,6 +10,18 @@ def create_app(): from middleware_tenant import resolve_tenant app.before_request(resolve_tenant) + # ─── Server-side guard: workshop/mechanic users only see /pos/workshop ────── + @app.before_request + def restrict_workshop_users(): + path = request.path + if not path.startswith('/pos/'): + return + if path == '/pos/workshop' or path.startswith('/pos/static/') or path.startswith('/pos/api/') or path == '/pos/sw.js' or path == '/pos/login' or path.startswith('/pos/login'): + return + role = request.cookies.get('pos_role', '').lower() + if role in ('workshop', 'mechanic'): + return redirect('/pos/workshop') + # ─── PWA: Service Worker must be served from /pos/ scope ────── @app.route('/pos/sw.js') def pos_sw(): diff --git a/pos/blueprints/auth_bp.py b/pos/blueprints/auth_bp.py index be66e9b..8bc60e6 100644 --- a/pos/blueprints/auth_bp.py +++ b/pos/blueprints/auth_bp.py @@ -5,7 +5,7 @@ import jwt import bcrypt import time from datetime import datetime, timezone, timedelta -from flask import Blueprint, request, jsonify, g +from flask import Blueprint, request, jsonify, g, make_response from config import JWT_SECRET, JWT_ACCESS_EXPIRES, PIN_MAX_ATTEMPTS_PER_MINUTE, PIN_LOCKOUT_THRESHOLD, PIN_LOCKOUT_MINUTES from tenant_db import get_tenant_conn, get_master_conn @@ -157,11 +157,17 @@ def login_pin(): } token = jwt.encode(payload, JWT_SECRET, algorithm='HS256') - return jsonify({ + response = make_response(jsonify({ 'token': token, 'employee': matched_employee, 'permissions': permissions - }) + })) + # Cookie used by server-side route guards; JS can also read it for quick checks. + response.set_cookie( + 'pos_role', matched_employee['role'], + path='/pos', samesite='Lax', httponly=False + ) + return response @auth_bp.route('/employees/', methods=['GET']) diff --git a/pos/static/js/app-init.js b/pos/static/js/app-init.js index 6dd725c..2365003 100644 --- a/pos/static/js/app-init.js +++ b/pos/static/js/app-init.js @@ -118,6 +118,7 @@ localStorage.removeItem('pos_employee'); localStorage.removeItem('pos_tenant_id'); localStorage.removeItem('pos_cart'); + document.cookie = 'pos_role=; path=/pos; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT'; window.location.href = '/pos/login'; }; diff --git a/pos/static/js/login.js b/pos/static/js/login.js index 8dc887b..bb36ec9 100644 --- a/pos/static/js/login.js +++ b/pos/static/js/login.js @@ -82,6 +82,7 @@ localStorage.setItem('pos_token', result.data.token); localStorage.setItem('pos_employee', JSON.stringify(result.data.employee)); localStorage.setItem('pos_tenant_id', tenantId); + document.cookie = 'pos_role=' + (result.data.employee.role || '') + '; path=/pos; SameSite=Lax'; window.location.href = '/pos/catalog'; }) .catch(function() { diff --git a/pos/static/js/login.min.js b/pos/static/js/login.min.js index 2d990fd..18bb0db 100644 --- a/pos/static/js/login.min.js +++ b/pos/static/js/login.min.js @@ -1 +1 @@ -!function(){"use strict";var t="",e=document.querySelectorAll("#pinDots .pin-dot"),n=document.getElementById("loginError"),o=new URLSearchParams(window.location.search).get("tenant")||localStorage.getItem("pos_tenant_id"),a=localStorage.getItem("pos_device_id");function i(){e.forEach((function(e,n){e.classList.toggle("filled",n=4||(t+=e,i(),n.textContent="",4===t.length&&submitPin())},window.clearPin=function(){t="",i(),n.textContent=""},window.submitPin=function(){4===t.length&&(n.textContent="",fetch("/pos/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tenant_id:parseInt(o),pin:t,device_id:a})}).then((function(t){return t.json().then((function(e){return{ok:t.ok,data:e}}))})).then((function(t){if(!t.ok)return n.textContent=t.data.error||"Error de autenticacion",void clearPin();localStorage.setItem("pos_token",t.data.token),localStorage.setItem("pos_employee",JSON.stringify(t.data.employee)),localStorage.setItem("pos_tenant_id",o),window.location.href="/pos/catalog"})).catch((function(){n.textContent="Error de conexion",clearPin()})))},document.addEventListener("keydown",(function(t){t.key>="0"&&t.key<="9"?addDigit(t.key):"Backspace"===t.key?clearPin():"Enter"===t.key&&submitPin()}));var r=localStorage.getItem("pos_token");r&&o&&(!function(t){try{var e=t.split(".");if(3!==e.length)return!1;var n=e[1].replace(/-/g,"+").replace(/_/g,"/"),o=JSON.parse(atob(n));return!!o.exp&&1e3*o.exp>Date.now()+3e4}catch(t){return!1}}(r)?(localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee")):window.location.href="/pos/catalog")}(); \ No newline at end of file +!function(){"use strict";var t="",e=document.querySelectorAll("#pinDots .pin-dot"),n=document.getElementById("loginError"),o=new URLSearchParams(window.location.search).get("tenant")||localStorage.getItem("pos_tenant_id"),a=localStorage.getItem("pos_device_id");function i(){e.forEach((function(e,n){e.classList.toggle("filled",n=4||(t+=e,i(),n.textContent="",4===t.length&&submitPin())},window.clearPin=function(){t="",i(),n.textContent=""},window.submitPin=function(){4===t.length&&(n.textContent="",fetch("/pos/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tenant_id:parseInt(o),pin:t,device_id:a})}).then((function(t){return t.json().then((function(e){return{ok:t.ok,data:e}}))})).then((function(t){if(!t.ok)return n.textContent=t.data.error||"Error de autenticacion",void clearPin();localStorage.setItem("pos_token",t.data.token),localStorage.setItem("pos_employee",JSON.stringify(t.data.employee)),localStorage.setItem("pos_tenant_id",o),document.cookie="pos_role="+(t.data.employee.role||"")+"; path=/pos; SameSite=Lax",window.location.href="/pos/catalog"})).catch((function(){n.textContent="Error de conexion",clearPin()})))},document.addEventListener("keydown",(function(t){t.key>="0"&&t.key<="9"?addDigit(t.key):"Backspace"===t.key?clearPin():"Enter"===t.key&&submitPin()}));var r=localStorage.getItem("pos_token");r&&o&&(!function(t){try{var e=t.split(".");if(3!==e.length)return!1;var n=e[1].replace(/-/g,"+").replace(/_/g,"/"),o=JSON.parse(atob(n));return!!o.exp&&1e3*o.exp>Date.now()+3e4}catch(t){return!1}}(r)?(localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee")):window.location.href="/pos/catalog")}(); \ No newline at end of file diff --git a/pos/templates/accounting.html b/pos/templates/accounting.html index 6913682..4a0b754 100644 --- a/pos/templates/accounting.html +++ b/pos/templates/accounting.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/catalog.html b/pos/templates/catalog.html index 9beea2a..b5416ab 100644 --- a/pos/templates/catalog.html +++ b/pos/templates/catalog.html @@ -17,20 +17,7 @@ - - - + diff --git a/pos/templates/config.html b/pos/templates/config.html index 2724bc4..50e03bb 100644 --- a/pos/templates/config.html +++ b/pos/templates/config.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/customers.html b/pos/templates/customers.html index aeea3b8..604d211 100644 --- a/pos/templates/customers.html +++ b/pos/templates/customers.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/dashboard.html b/pos/templates/dashboard.html index cab69cd..bd83d50 100644 --- a/pos/templates/dashboard.html +++ b/pos/templates/dashboard.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/diagrams.html b/pos/templates/diagrams.html index 32d8abd..d9fc58f 100644 --- a/pos/templates/diagrams.html +++ b/pos/templates/diagrams.html @@ -17,20 +17,7 @@ - - - +
diff --git a/pos/templates/fleet.html b/pos/templates/fleet.html index db54aa1..cc3a573 100644 --- a/pos/templates/fleet.html +++ b/pos/templates/fleet.html @@ -15,20 +15,7 @@ - - - +
diff --git a/pos/templates/historical_sales.html b/pos/templates/historical_sales.html index 70c2b6e..5894033 100644 --- a/pos/templates/historical_sales.html +++ b/pos/templates/historical_sales.html @@ -34,20 +34,7 @@ .pagination button:disabled { opacity: 0.5; cursor: not-allowed; } .pagination span { font-size: 13px; color: #4b5563; } .loading { text-align: center; padding: 40px; color: #6b7280; } - - - +

📊 Ventas Históricas - Atlas

diff --git a/pos/templates/inventory.html b/pos/templates/inventory.html index 38ab96d..28bf9c3 100644 --- a/pos/templates/inventory.html +++ b/pos/templates/inventory.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/invoicing.html b/pos/templates/invoicing.html index 804ff98..0e1ad87 100644 --- a/pos/templates/invoicing.html +++ b/pos/templates/invoicing.html @@ -15,20 +15,7 @@ - - - + diff --git a/pos/templates/marketplace.html b/pos/templates/marketplace.html index ef12c06..0feca55 100644 --- a/pos/templates/marketplace.html +++ b/pos/templates/marketplace.html @@ -15,20 +15,7 @@ - - - +