feat: subdomain routing por tenant — refac-xxx.nexusautoparts.com
- Nginx wildcard config: *.nexusautoparts.com routes to POS app with X-Tenant-Subdomain header - middleware_tenant.py: resolves subdomain -> tenant_id via nexus_master.tenants.subdomain - auth_bp: login and employee list endpoints accept tenant from subdomain, URL param, or body - login.html: auto-detects tenant from subdomain, shows business name, falls back to ?tenant=ID - tenant_manager: generates subdomain slug from business name on provision_tenant() - Migration v1.2: adds subdomain column + unique index to tenants table - setup-nginx.sh: one-command install script for the nginx config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import jwt
|
||||
import bcrypt
|
||||
import time
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
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
|
||||
|
||||
@@ -48,9 +48,16 @@ def _record_attempt(device_id, success):
|
||||
|
||||
@auth_bp.route('/login', methods=['POST'])
|
||||
def login_pin():
|
||||
"""Login with tenant_id + PIN + device_id."""
|
||||
"""Login with tenant_id + PIN + device_id.
|
||||
|
||||
tenant_id can come from:
|
||||
1. Subdomain (resolved by middleware_tenant into g.tenant_id)
|
||||
2. POST body tenant_id field
|
||||
3. Both (subdomain takes precedence)
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
tenant_id = data.get('tenant_id')
|
||||
# Subdomain-resolved tenant takes priority over body param
|
||||
tenant_id = getattr(g, 'tenant_id', None) or data.get('tenant_id')
|
||||
pin = data.get('pin', '')
|
||||
device_id = data.get('device_id', request.headers.get('X-Device-Id', 'unknown'))
|
||||
# Optional: branch_id from the device for PIN search optimization
|
||||
@@ -158,10 +165,24 @@ def login_pin():
|
||||
|
||||
|
||||
@auth_bp.route('/employees/<int:tenant_id>', methods=['GET'])
|
||||
def list_login_employees(tenant_id):
|
||||
"""Public endpoint: list employees for the login screen (names + roles only, no sensitive data)."""
|
||||
@auth_bp.route('/employees', methods=['GET'])
|
||||
def list_login_employees(tenant_id=None):
|
||||
"""Public endpoint: list employees for the login screen (names + roles only, no sensitive data).
|
||||
|
||||
tenant_id comes from URL path, subdomain, or ?tenant= param.
|
||||
"""
|
||||
# Resolve tenant_id: URL path > subdomain > query param
|
||||
tid = tenant_id or getattr(g, 'tenant_id', None)
|
||||
if not tid:
|
||||
try:
|
||||
tid = int(request.args.get('tenant', 0))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if not tid:
|
||||
return jsonify({'error': 'Tenant not specified'}), 400
|
||||
|
||||
try:
|
||||
conn = get_tenant_conn(tenant_id)
|
||||
conn = get_tenant_conn(tid)
|
||||
except ValueError:
|
||||
return jsonify({'error': 'Tenant not found'}), 404
|
||||
|
||||
|
||||
Reference in New Issue
Block a user