
    ^i%                        U d Z ddlmZ ddlZddlZddlmZ ddlmZ  ej                  d      Z
 eh d      Zded	<   dd
ZddZ	 ddlmZ ddlmZ ddlmZ  G d de      ZdZddZddZy# e$ r dZdZY w xY w)a  RLM Neo-Cortex -- Tenant ID Extraction Middleware.

Every incoming request to the RLM API must carry a tenant identity so the
gateway can enforce per-tenant isolation, quota, and entitlements.

Two extraction strategies are supported (tried in order):

1. **JWT Bearer token** (``Authorization: Bearer <token>``)
   The JWT payload must include a ``tenant_id`` or ``sub`` claim that is a
   valid UUID v4.  The token is verified with HS256 against ``JWT_SECRET``.

2. **API key header** (default: ``X-Api-Key: <api_key>``)
   The header name is configurable via the ``API_KEY_HEADER`` env var.
   The key is looked up in Redis/PostgreSQL via the EntitlementLedger.
   The first 8 characters are used as a fast-path Redis key prefix.

If neither strategy produces a valid tenant UUID the middleware returns
HTTP 401 with a JSON ``{"error": "missing_tenant_identity"}`` body.

The extracted tenant UUID is attached to ``request.state.tenant_id`` so
downstream route handlers can access it without re-parsing headers.

Story: integration-layer-middleware
    )annotationsN)Optional)UUIDzcore.rlm.middleware>   /docs/redoc/health/openapi.json/zfrozenset[str]_EXEMPT_PATHSc                   	 ddl }|j                  | ||g      }|j                  d      xs |j                  d      }|rt        t	        |            S 	 y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)a  Decode a JWT and extract the tenant UUID from payload.

    Returns None if the token is invalid, expired, or does not contain a
    recognised UUID claim.

    Args:
        token: Raw JWT string (without 'Bearer ' prefix).
        secret: HS256 signing secret.
        algorithm: JWT algorithm (usually 'HS256').

    Returns:
        UUID extracted from 'tenant_id' or 'sub' claim, or None.
    r   N)
algorithms	tenant_idsubzJWT decode failed: %s)jwtdecodegetr   str	Exceptionloggerdebug)tokensecret	algorithmpyjwtpayloadraw_idexcs          ,/mnt/e/genesis-system/core/rlm/middleware.py_decode_jwtr   0   s    3,,uf),E[)?W[[-?F$$    3,c223s   AA 	B  A;;B c                   K   t         j                  j                  dd      }t         j                  j                  dd      }t        |       dk\  r| dd n| }|r]	 ddlm} |j                  |dd	      }|j                  d
|        d{   }|j                          d{    |rt        |      S 	 |r	 ddl}|j                  |d       d{   }	|	j                  d|        d{   }
|	j                          d{    |
rxt        t!        |
d               }|r]	 ddlm} |j                  |dd	      }|j#                  d
| t!        |      d       d{    |j                          d{    |S |S 	 yy7 7 # t        $ r }t        j                  d|       Y d}~d}~ww xY w7 7 7 7 Y7 C# t        $ r Y |S w xY w# t        $ r }t        j                  d|       Y d}~yd}~ww xY ww)u{  Resolve an API key to a tenant UUID via Redis fast-path, then PostgreSQL.

    Redis key format: ``rlm:apikey:<api_key_prefix8>`` → ``<tenant_uuid>``
    If not in Redis, falls back to querying the ``rlm_tenant_api_keys`` table.

    Args:
        api_key: The raw API key string from the request header.

    Returns:
        UUID if the API key is recognised, else None.
    	REDIS_URL DATABASE_URL   Nr   T   )decode_responsessocket_timeoutzrlm:apikey:zRedis API-key lookup failed: %s   )timeoutzNSELECT tenant_id FROM rlm_tenant_api_keys WHERE api_key = $1 AND active = TRUEr   i,  )exz$PostgreSQL API-key lookup failed: %s)osenvironr   lenredis.asyncioasynciofrom_urlacloser   r   r   r   asyncpgconnectfetchrowcloser   set)api_key	redis_urlpg_dsnprefix8aioredisrcachedr   r2   connrowtids               r   _lookup_api_keyrA   M   s     

{B/I

~r2F \Q.gbqkGG 	A,!!)dST!UA55;wi!899F((*F|#  	F ;;D` C **,3s;/018$--i$_`-aeek'$;SX#eNNNhhj(( 
s
  A :  	ALL:C@@	A <  O($ 
  	FLL?EE	Fs  AG>1F FF $F
%F 5G>:G F8G .F:/G F<G '=G $F>%G <G =G G G>G G>G>F 
F 	F5F0+G>0F55G>8G :G <G >G  G 	GG G>GG 	G;G61G>6G;;G>)BaseHTTPMiddleware)Request)JSONResponsec                      e Zd ZdZddZy)TenantMiddlewareu]  Extract tenant UUID from JWT or API key and attach to request state.

        Attach to a FastAPI app::

            from core.rlm.middleware import TenantMiddleware
            app.add_middleware(TenantMiddleware)

        After this middleware runs, downstream handlers can access::

            tenant_id: UUID = request.state.tenant_id  # may be None for exempt paths

        Configuration (from os.environ, no hardcoded values):
            JWT_SECRET      — HS256 signing secret
            JWT_ALGORITHM   — default HS256
            API_KEY_HEADER  — header name, default X-Api-Key
        c                  K   d |j                   _        |j                  j                  t        v r ||       d {   S t        |       d {   }|Et        j                  d|j                  |j                  j                         t        dddd      S ||j                   _        t        j                  d||j                  |j                  j                          ||       d {   S 7 7 7 w)Nz"Tenant extraction failed for %s %si  missing_tenant_identityz_Provide a valid JWT Bearer token (with tenant_id claim) or an API key in the configured header.)errordetail)status_codecontentz!Tenant %s authenticated for %s %s)stater   urlpathr   _extract_tenantr   warningmethodrD   r   )selfrequest	call_nextr   s       r   dispatchzTenantMiddleware.dispatch   s     &*GMM# {{=0&w///-g66I 8NNGKK$4$4 $ #!:F	 	 '0GMM#LL37>>7;;+;+; #7+++3 06. ,s4   9C7C1C7C3BC7,C5-C73C75C7N)rT   rC   )__name__
__module____qualname____doc__rV        r   rF   rF      s    	"	,r\   rF   TFc                  K   t         j                  j                  dd      }t         j                  j                  dd      }t         j                  j                  dd      }| j                  j                  dd      }|j	                  d      r!|r|t        d      d	 }t        |||      }|r|S | j                  j                  |d      }|rt        |       d	{   }|r|S y	7 	w)
zTry JWT, then API key; return UUID or None.

    Separated from the middleware class so it can be unit-tested independently
    and reused by the MCP bridge.
    
JWT_SECRETr"   JWT_ALGORITHMHS256API_KEY_HEADER	X-Api-KeyAuthorizationBearer N)r+   r,   r   headers
startswithr-   r   rA   )rT   
jwt_secretjwt_algorithmapi_key_headerauth_headerr   r@   r7   s           r   rP   rP      s      JJNN<4JJJNN?G<MZZ^^$4kBN //%%or:Ki(ZC	NO,%];J oo!!."5G#G,,J	 -s   CC)C'
C)c                0  K   t         j                  j                  dd      }t         j                  j                  dd      }t         j                  j                  dd      j                         }| j	                         D ci c]  \  }}|j                         | }}}|j                  dd      }|j                  d      r!|r|t        d      d	 }t        |||      }	|	r|	S |j                  |d      }
|
rt        |
       d	{   S y	c c}}w 7 w)
u%  Extract tenant UUID from a plain dict of headers (no Request object).

    Used by the MCP bridge which doesn't have a Starlette Request.

    Args:
        headers: Dict of header name → value (case-insensitive lookup).

    Returns:
        UUID if a valid tenant is found, else None.
    r^   r"   r_   r`   ra   rb   authorizationrd   N)	r+   r,   r   loweritemsrf   r-   r   rA   )re   rg   rh   ri   kv
normalisedauthr   r@   r7   s              r   extract_tenant_from_headersrs      s      ZZ^^L"5JZZ^^OW=MZZ^^$4kBHHJN ,3==?;41a!'')Q,;J; >>/2.Dy!jS^_%%];J nn^R0G$W--- < .s   BDDA)DD	D)r   r   r   r   r   r   returnOptional[UUID])r7   r   rt   ru   )rT   z	'Request'rt   ru   )re   dictrt   ru   )rZ   
__future__r   loggingr+   typingr   uuidr   	getLoggerr   	frozensetr   __annotations__r   rA   starlette.middleware.baserB   starlette.requestsrC   starlette.responsesrD   rF   _MIDDLEWARE_AVAILABLEImportErrorrP   rs   r[   r\   r   <module>r      s   0 #  	  			0	1 !* + !~ :6z;<*00,- 0,d !@S  !s   A0 0	A<;A<