# 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