Backend — Fase 3

Admin API

11 AWS Lambda functions behind API Gateway — JWT-secured, multi-tenant, DynamoDB-powered.

Architecture

Client (Dashboard)
  │
  ├── Authorization: Bearer <Clerk JWT>
  ├── x-organization-id: <org_id>
  │
  ▼
API Gateway (eu-south-2)
  │
  ├── OPTIONS → handle_options() [CORS preflight]
  ├── GET /health → health_check()
  │
  ├── GET /users → list_users()
  ├── GET /users/{userId} → get_user()
  │
  ├── GET /fingerprints → list_fingerprints()
  ├── GET /fingerprints/{fp} → get_fingerprint()
  │
  ├── GET /domainprints → list_domainprints()
  ├── GET /domainprints/{dp} → get_domainprint()
  │
  ├── GET /fraud/stats → get_fraud_stats()
  ├── GET /identity-graph → get_identity_graph()
  ├── GET /attribution-stats → get_attribution_stats()
  └── GET /account-sharing → get_account_sharing_stats()
          │
          ▼
      DynamoDB (fingerprint-events)
      ├── PK: FP#{fingerprint}
      ├── SK: EVENT#{timestamp}
      ├── GSI_ORG: organizationId + timestamp
      └── GSI_USER: user_id + timestamp

Security Stack

JWT RS256

Clerk JWKS verification — firma RS256 validada contra public keys

CORS Whitelist

Solo getimpress.io y localhost:3000 — Origin validado en cada request

Multi-Tenant

x-organization-id header filtra datos — zero cross-org leaks

Superadmin Bypass

role=superadmin en JWT metadata — table scan sin org filter

JWT Verification Flow

def decode_jwt_payload(token):
    """RS256 signature verification via Clerk JWKS"""
    return verify_clerk_jwt(token)  # jwt_verifier.py

def get_org_id_from_event(event):
    # 1. Check x-organization-id header
    org_id = headers.get('x-organization-id')
    if org_id: return org_id

    # 2. No org? Check if superadmin
    jwt = decode_jwt_payload(auth_header)
    if is_super_admin(jwt):  # metadata.role == 'superadmin'
        return None  # → table.scan() (all orgs)

    # 3. Not superadmin, no org → 403
    raise Exception('Forbidden: No organization selected')

11 Endpoints

LambdaRouteDescription
list-usersGET /usersList all users with event counts, risk scores, domains
get-userGET /users/{id}User detail: events, fingerprints, domainprints, stats
list-fingerprintsGET /fingerprintsList fingerprints with collision detection
get-fingerprintGET /fingerprints/{fp}Fingerprint detail: events, user associations
list-domainprintsGET /domainprintsList domainprints with cross-domain stats
get-domainprintGET /domainprints/{dp}Domainprint detail: users, fingerprints, domains
get-fraud-statsGET /fraud/statsFraud dashboard: risk levels, signals, impossible travel
get-identity-graphGET /identity-graphCross-domain identity relationships
attribution-statsGET /attributionCampaign attribution aggregated stats
account-sharingGET /account-sharingAccount sharing detection stats
get-healthGET /healthService health + DynamoDB ping

Common Patterns

DecimalEncoder

All 11 Lambdas share a custom JSON encoder for DynamoDB Decimal types:

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return int(obj) if obj % 1 == 0 else float(obj)
        return super().default(obj)

CORS Headers

Origin-validated CORS with Vary: Origin:

ALLOWED_ORIGINS = [
    'https://getimpress.io',
    'https://www.getimpress.io',
    'http://localhost:3000',
]

def cors_headers(event=None):
    origin = headers.get('origin')
    allowed = origin if origin in ALLOWED_ORIGINS
              else ALLOWED_ORIGINS[0]
    return {
        'Access-Control-Allow-Origin': allowed,
        'Vary': 'Origin'
    }

Multi-Tenant Query Pattern

Every endpoint isolates data by organization:

if org_id:
    # Normal user → GSI_ORG query (fast, isolated)
    response = table.query(
        IndexName='GSI_ORG',
        KeyConditionExpression='organizationId = :orgId',
        ExpressionAttributeValues={':orgId': org_id}
    )
else:
    # Superadmin → full table scan (all orgs)
    response = table.scan(Limit=5000)

Deep Dive: Endpoint Reference

Request/response schemas, query parameters, and GSI usage for each endpoint.

View Endpoint Reference