# Tanflow SSO User Data Mapping

This document outlines all user information available from Tanflow IAM Suite and how it maps to our User model for user creation.

## Tanflow Userinfo Endpoint Response

Tanflow uses **OpenID Connect (OIDC) standard claims** via the `/protocol/openid-connect/userinfo` endpoint. The following fields are available:

### Standard OIDC Claims (Available from Tanflow)

| Tanflow Field | OIDC Standard Claim | Type | Description | Currently Extracted |
|--------------|---------------------|------|--------------|-------------------|
| `sub` | `sub` | string | **REQUIRED** - Subject identifier (unique user ID) | ✅ Yes (as `oktaSub`) |
| `email` | `email` | string | Email address | ✅ Yes |
| `email_verified` | `email_verified` | boolean | Email verification status | ❌ No |
| `preferred_username` | `preferred_username` | string | Preferred username (fallback for email) | ✅ Yes (fallback) |
| `name` | `name` | string | Full display name | ✅ Yes (as `displayName`) |
| `given_name` | `given_name` | string | First name | ✅ Yes (as `firstName`) |
| `family_name` | `family_name` | string | Last name | ✅ Yes (as `lastName`) |
| `phone_number` | `phone_number` | string | Phone number | ✅ Yes (as `phone`) |
| `phone_number_verified` | `phone_number_verified` | boolean | Phone verification status | ❌ No |
| `address` | `address` | object | Address object (structured) | ❌ No |
| `locale` | `locale` | string | User locale (e.g., "en-US") | ❌ No |
| `picture` | `picture` | string | Profile picture URL | ❌ No |
| `website` | `website` | string | Website URL | ❌ No |
| `profile` | `profile` | string | Profile page URL | ❌ No |
| `birthdate` | `birthdate` | string | Date of birth | ❌ No |
| `gender` | `gender` | string | Gender | ❌ No |
| `zoneinfo` | `zoneinfo` | string | Timezone (e.g., "America/New_York") | ❌ No |
| `updated_at` | `updated_at` | number | Last update timestamp | ❌ No |

### Custom Tanflow Claims (May be available)

These are **custom claims** that Tanflow may include based on their configuration:

| Tanflow Field | Type | Description | Currently Extracted |
|--------------|------|-------------|-------------------|
| `employeeId` | string | Employee ID from HR system | ✅ Yes |
| `employee_id` | string | Alternative employee ID field | ✅ Yes (fallback) |
| `department` | string | Department/Division | ✅ Yes |
| `designation` | string | Job designation/position | ✅ Yes |
| `title` | string | Job title | ❌ No |
| `designation` | string | Job designation/position | ✅ Yes (as `designation`) |
| `employeeType` | string | Employee type (Dealer, Full-time, Contract, etc.) | ✅ Yes (as `jobTitle`) |
| `organization` | string | Organization name | ❌ No |
| `division` | string | Division name | ❌ No |
| `location` | string | Office location | ❌ No |
| `manager` | string | Manager name/email | ❌ No |
| `manager_id` | string | Manager employee ID | ❌ No |
| `cost_center` | string | Cost center code | ❌ No |
| `hire_date` | string | Date of hire | ❌ No |
| `office_location` | string | Office location | ❌ No |
| `country` | string | Country code | ❌ No |
| `city` | string | City name | ❌ No |
| `state` | string | State/Province | ❌ No |
| `postal_code` | string | Postal/ZIP code | ❌ No |
| `groups` | array | Group memberships | ❌ No |
| `roles` | array | User roles | ❌ No |

## Current Extraction Logic

**Location:** `Re_Backend/src/services/auth.service.ts` → `exchangeTanflowCodeForTokens()`

```typescript
const userData: SSOUserData = {
  oktaSub: tanflowSub, // Reuse oktaSub field for Tanflow sub
  email: tanflowUserInfo.email || tanflowUserInfo.preferred_username || '',
  employeeId: tanflowUserInfo.employeeId || tanflowUserInfo.employee_id || undefined,
  firstName: tanflowUserInfo.given_name || tanflowUserInfo.firstName || undefined,
  lastName: tanflowUserInfo.family_name || tanflowUserInfo.lastName || undefined,
  displayName: tanflowUserInfo.name || tanflowUserInfo.displayName || undefined,
  department: tanflowUserInfo.department || undefined,
  designation: tanflowUserInfo.designation || undefined, // Map designation to designation
  phone: tanflowUserInfo.phone_number || tanflowUserInfo.phone || undefined,
  // Additional fields
  manager: tanflowUserInfo.manager || undefined,
  jobTitle: tanflowUserInfo.employeeType || undefined, // Map employeeType to jobTitle
  postalAddress: tanflowUserInfo.address ? (typeof tanflowUserInfo.address === 'string' ? tanflowUserInfo.address : JSON.stringify(tanflowUserInfo.address)) : undefined,
  mobilePhone: tanflowUserInfo.mobile_phone || tanflowUserInfo.mobilePhone || undefined,
  adGroups: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups : undefined,
};
```

## User Model Fields Mapping

**Location:** `Re_Backend/src/models/User.ts`

| User Model Field | Tanflow Source | Required | Notes |
|-----------------|----------------|----------|-------|
| `userId` | Auto-generated UUID | ✅ | Primary key |
| `oktaSub` | `sub` | ✅ | Unique identifier from Tanflow |
| `email` | `email` or `preferred_username` | ✅ | Primary identifier |
| `employeeId` | `employeeId` or `employee_id` | ❌ | Optional HR system ID |
| `firstName` | `given_name` or `firstName` | ❌ | Optional |
| `lastName` | `family_name` or `lastName` | ❌ | Optional |
| `displayName` | `name` or `displayName` | ❌ | Auto-generated if missing |
| `department` | `department` | ❌ | Optional |
| `designation` | `designation` | ❌ | Optional |
| `phone` | `phone_number` or `phone` | ❌ | Optional |
| `manager` | `manager` | ❌ | Optional (extracted if available) |
| `secondEmail` | N/A | ❌ | Not available from Tanflow |
| `jobTitle` | `employeeType` | ❌ | Optional (maps employeeType to jobTitle) |
| `employeeNumber` | N/A | ❌ | Not available from Tanflow |
| `postalAddress` | `address` (structured) | ❌ | **NOT currently extracted** |
| `mobilePhone` | N/A | ❌ | Not available from Tanflow |
| `adGroups` | `groups` | ❌ | **NOT currently extracted** |
| `location` | `address`, `city`, `state`, `country` | ❌ | **NOT currently extracted** |
| `role` | Default: 'USER' | ✅ | Default role assigned |
| `isActive` | Default: true | ✅ | Auto-set to true |
| `lastLogin` | Current timestamp | ✅ | Auto-set on login |

## Recommended Enhancements

### 1. Extract Additional Fields

Consider extracting these fields if available from Tanflow:

```typescript
// Enhanced extraction (to be implemented)
const userData: SSOUserData = {
  // ... existing fields ...
  
  // Additional fields (already implemented)
  manager: tanflowUserInfo.manager || undefined,
  jobTitle: tanflowUserInfo.employeeType || undefined, // Map employeeType to jobTitle
  postalAddress: tanflowUserInfo.address ? (typeof tanflowUserInfo.address === 'string' ? tanflowUserInfo.address : JSON.stringify(tanflowUserInfo.address)) : undefined,
  mobilePhone: tanflowUserInfo.mobile_phone || tanflowUserInfo.mobilePhone || undefined,
  adGroups: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups : undefined,
  
  // Location object
  location: {
    city: tanflowUserInfo.city || undefined,
    state: tanflowUserInfo.state || undefined,
    country: tanflowUserInfo.country || undefined,
    office: tanflowUserInfo.office_location || undefined,
    timezone: tanflowUserInfo.zoneinfo || undefined,
  },
};
```

### 2. Log Available Fields

Add logging to see what Tanflow actually returns:

```typescript
logger.info('Tanflow userinfo response', {
  availableFields: Object.keys(tanflowUserInfo),
  hasEmail: !!tanflowUserInfo.email,
  hasEmployeeId: !!(tanflowUserInfo.employeeId || tanflowUserInfo.employee_id),
  hasDepartment: !!tanflowUserInfo.department,
  hasManager: !!tanflowUserInfo.manager,
  hasGroups: Array.isArray(tanflowUserInfo.groups),
  groupsCount: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups.length : 0,
  sampleData: {
    sub: tanflowUserInfo.sub?.substring(0, 10) + '...',
    email: tanflowUserInfo.email?.substring(0, 10) + '...',
    name: tanflowUserInfo.name,
  }
});
```

## User Creation Flow

1. **Token Exchange** → Get `access_token` from Tanflow
2. **Userinfo Call** → Call `/protocol/openid-connect/userinfo` with `access_token`
3. **Extract Data** → Map Tanflow fields to `SSOUserData` interface
4. **User Lookup** → Check if user exists by `email`
5. **Create/Update** → Create new user or update existing user
6. **Generate Tokens** → Generate JWT access/refresh tokens

## Testing Recommendations

1. **Test with Real Tanflow Account**
   - Log actual userinfo response
   - Document all available fields
   - Verify field mappings

2. **Handle Missing Fields**
   - Ensure graceful fallbacks
   - Don't fail if optional fields are missing
   - Log warnings for missing expected fields

3. **Validate Required Fields**
   - `sub` (oktaSub) - REQUIRED
   - `email` or `preferred_username` - REQUIRED

## Next Steps

1. ✅ **Current Implementation** - Basic OIDC claims extraction
2. 🔄 **Enhancement** - Extract additional custom claims (manager, groups, location)
3. 🔄 **Logging** - Add detailed logging of Tanflow response
4. 🔄 **Testing** - Test with real Tanflow account to see actual fields
5. 🔄 **Documentation** - Update this doc with actual Tanflow response structure

## Notes

- Tanflow uses **Keycloak** under the hood (based on URL structure)
- Keycloak supports custom user attributes that may be available
- Some fields may require specific realm/client configuration in Tanflow
- Contact Tanflow support to confirm available custom claims

