# Auth0 Identity Analysis

A lightweight audit tool for analyzing Auth0 user identities and their associated Neo4j data. This tool enables near real-time identity auditing with a static, backend-less frontend SPA.

## Purpose

This application is designed as an **audit tool** that allows engineers and non-engineers alike to quickly understand user state **without requiring Auth0 access**.

### Why This Tool?

- **Auth0 access is limited** - not everyone has credentials
- **Settings app doesn't show everything** - Neo4j identity data, and full org membership aren't visible
- **Support debugging** - understanding a user's brand experience requires correlating Auth0, org membership, and Neo4j data.

This tool consolidates all identity data into a searchable, self-service interface.

### How It Works

- **Runs periodically** (daily/weekly) via the backend pipeline to export fresh identity data
- **Produces a static output** (`combined_users.json.gz`) containing all user data
- **Deploys as a static SPA** - the compressed JSON data and frontend assets are hosted together with no backend
- **Works like our other private apps** - a self-contained static site that can be hosted like our new private SPA.

This architecture ensures:
- ✅ No backend infrastructure to maintain
- ✅ Fast, offline-capable searches via IndexedDB
- ✅ Data freshness controlled by pipeline schedule
- ✅ Simple deployment model

---

## Backend

The backend is a Python-based data pipeline that exports data from Auth0 and Neo4j, then combines it into a single compressed file.

### Pipeline Overview

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Export Auth0   │     │  Export Orgs    │     │ Export Members  │
│     Users       │     │                 │     │                 │
└────────┬────────┘     └────────┬────────┘     └────────┬────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Export Neo4j Identities                      │
│              (uses Auth0 user identity_ids as input)             │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                       Combine All Data                           │
│            (CombinedUser model → compressed JSON.gz)             │
└─────────────────────────────────────────────────────────────────┘
```

### 1. Export Auth0 Users

**Script:** `backend/src/auth0/export_users.py`

Uses the Auth0 Management API job-based export system:

1. **Create Export Job** - Submits a request to Auth0 to export users with specific fields
2. **Poll for Completion** - Checks job status every 5 seconds (300s timeout)
3. **Download from S3** - Auth0 provides a pre-signed S3 URL with the gzipped export
4. **Decompress & Save** - Extracts and saves as JSON

**Exported Fields:**
- `user_id`, `nickname`, `name`, `email`, `email_verified`
- `connection`, `created_at`, `updated_at`, `last_login`
- `identity_id` (critical for Neo4j matching)

### 2. Export Organizations

**Script:** `backend/src/auth0/export_orgs.py`

Simple Auth0 CLI wrapper that fetches all organizations:

```bash
auth0 orgs list --json --number 1000
```

**Output:** `orgs_{timestamp}.json` containing org `id`, `name`, and `display_name`

### 3. Export Organization Members

**Script:** `backend/src/auth0/export_org_members.py`

**⚠️ Auth0 Limitation:** Auth0 does not provide organization membership data in bulk exports or via the export job API. There is no way to get "which orgs does this user belong to?" in a single call. Instead, you must iterate through each organization and fetch its members individually.

This means:
- ~40 minutes to export all organization memberships
- Rate limiting required to avoid API throttling
- No bulk alternative available from Auth0

**Why this matters:** Organization membership is critical for understanding a user's state - it directly relates to their brand experience. We receive support issues where users have unexpected brand experiences, and this data is essential for debugging those cases.

The script iterates through all organizations and fetches their members via the Management API:

- Uses checkpoint pagination (`from={next_token}`) to handle large member lists
- Rate limited: 0.75s between API calls, 2s between organizations
- Maps members to their organizations

**Output:** `org_members_{timestamp}.json` with structure:
```json
{
  "org_id": {
    "name": "org-name",
    "display_name": "Org Display Name",
    "members": [
      {"user_id": "auth0|...", "email": "...", "name": "..."}
    ]
  }
}
```

### 4. Export Neo4j Identities

**Script:** `backend/src/neo4j/export_identities.py`

Batch-queries Neo4j using Auth0 user identity IDs:

1. **Read Auth0 Export** - Loads the Auth0 users JSON
2. **Extract Identity IDs** - Gets `identity_id` from each Auth0 user
3. **Batch Query** - Sends 1000 IDs per batch to Neo4j
4. **Execute Cypher** - Uses `backend/src/neo4j/cypher/get_identities_batch.cypher`

**Key Matching:** `Auth0User.identity_id` → `Neo4jIdentity.id`

The Cypher query retrieves identity details including profiles, active status, and employee flags.

### 5. Combine Data

**Script:** `backend/src/combine_users.py`

Merges all data sources into a single `CombinedUser` model:

```python
@dataclass
class CombinedUser:
    auth0_data: Auth0User       # All Auth0 fields + organizations
    neo4j_data: Neo4jIdentity   # Neo4j identity (nullable if not found)
```

**Combination Logic:**
1. Build lookup dictionaries for fast matching
2. For each Auth0 user:
   - Match organizations by `user_id`
   - Match Neo4j identity by `identity_id`
3. Create `CombinedUser` instances

**Output:** `combined_users_{timestamp}.json.gz` (gzip-compressed JSON)

---

## Frontend

A lightweight Suite app that loads the compressed data into IndexedDB for fast, offline-capable searching.

### Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         App Bootstrap                            │
│                    (Routes + DataProvider)                       │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                      DataContext (React)                         │
│       (State: loading, pagination, sorting, search, filters)     │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                    IndexedDB via Dexie                           │
│                   (auth0-identity-db)                            │
└─────────────────────────────────────────────────────────────────┘
```

### Data Loading

**File:** `frontend/src/data/db.ts`

On app initialization:

1. **Check Cache** - If IndexedDB already has users, skip fetch
2. **Fetch Data** - Download `combined_users.json.gz` from static hosting
3. **Decompress** - Use browser's `DecompressionStream('gzip')`
4. **Validate** - Zod schema ensures data integrity
5. **Transform** - Add `searchTokens` for fast indexed search
6. **Store** - Bulk insert into IndexedDB

### IndexedDB & Dexie

[Dexie.js](https://dexie.org/) is used as a wrapper around IndexedDB, providing:

- Async/await API
- Query chaining and filtering
- Index management
- Bulk operations

**Database Schema:**
```typescript
class UsersDatabase extends Dexie {
    users!: Table<CombinedUserWithTokens, string>;

    constructor() {
        super('auth0-identity-db');
        this.version(6).stores({
            users: 'auth0Id, auth0Email, auth0Name, auth0IdentityId, auth0LastLogin, neo4jEmail, neo4jLastName, neo4jFirstName, *searchTokens',
        });
    }
}
```

### Search Tokens & Indexes

For fast search, each user record includes pre-computed `searchTokens`:

```typescript
searchTokens: [
    user.auth0Id.toLowerCase(),
    user.auth0Email.toLowerCase(),
    user.auth0Name.toLowerCase(),
    user.auth0IdentityId?.toLowerCase(),
    user.neo4jEmail?.toLowerCase(),
    user.neo4jFirstName?.toLowerCase(),
    user.neo4jLastName?.toLowerCase(),
].filter(Boolean)
```

The `*searchTokens` index uses Dexie's **multi-entry index** feature, allowing efficient prefix searches:

```typescript
db.users.where('searchTokens').startsWith(searchTerm.toLowerCase())
```

This enables:
- ✅ Case-insensitive search
- ✅ Search by any indexed field
- ✅ Prefix matching (type-ahead)
- ✅ Index-powered performance

### Pages

#### Home Page (`/`)

**File:** `frontend/src/pages/home/HomePage.tsx`

- Paginated table of all users (500 per page default)
- Live search input with instant results
- Sortable columns (Auth0 ID, Name, Email, Last Login, etc.)
- Active status indicators
- Click-through to user detail page
- "Refresh Data" button to clear cache and reload

#### User Detail Page (`/users/:id`)

**File:** `frontend/src/pages/users/UserPage.tsx`

Displays comprehensive user information:

- **Auth0 Details** - All Auth0 fields plus organization memberships
- **Neo4j Details** - Identity data from Neo4j (or "No data found")
- **Profiles** - Nested grid of tenant/profile relationships
- **Quick Links** - Direct links to Auth0 Dashboard and Settings

---

## Deployment - TBD

1. Run backend pipeline to generate `combined_users_{timestamp}.json.gz` on a schedule.
2. Copy the compressed file to S3/
3. Build the frontend: `pnpm build`
4. Deploy the `build/` folder to a private SPA S3 bucket.