Skip to Content
📚 MyStoryFlow Docs — Your guide to preserving family stories

User Impersonation System

The MyStoryFlow User Impersonation System allows administrators to securely access user accounts for customer support, debugging, and compliance purposes while maintaining strict security protocols and comprehensive audit trails.

🛡️ Security Framework

Core Security Principles

  1. Role-Based Access Control: Only users with ‘admin’ or ‘support’ roles can impersonate
  2. Time-Limited Sessions: Sessions automatically expire after 4 hours
  3. Comprehensive Auditing: Every impersonation start/end is logged via AuditService
  4. Session Tracking: All active sessions stored in database with full context
  5. Simple Token-Based Authentication: UUID-based session tokens for secure access

Security Architecture

The system implements a straightforward security model:

  • Role Validation: Checks user has ‘admin’ or ‘support’ role before allowing impersonation
  • Session Management: Maximum 4-hour session duration with automatic expiration
  • Audit Logging: High-severity audit logs for session start/end events
  • Database Tracking: All sessions stored in impersonation_sessions table
  • No JWT Complexity: Uses simple UUID tokens instead of JWT for session management

🏗️ System Architecture

Core Components

  1. UserImpersonationService (apps/admin-app/src/lib/user-impersonation-service.ts) - Manages session lifecycle
  2. AuditService (apps/admin-app/src/lib/audit-service.ts) - Records all impersonation events
  3. Database Table (impersonation_sessions) - Stores active and historical sessions
  4. Admin UI Pages - User search and impersonation initiation
  5. Web App Auth Flow - Handles impersonation token redirect (currently not implemented)

Actual Data Flow

The current implementation follows this flow:

  1. Admin navigates to /dashboard/users or /dashboard/users/impersonate
  2. Admin searches for and selects a user to impersonate
  3. System calls userManagementService.createImpersonationToken(userId, adminUserId)
  4. A simple token is generated: impersonate_${userId}_${Date.now()}
  5. Admin is shown URL: ${MAIN_APP_URL}/auth-redirect?impersonate=${token}
  6. New tab opens with the impersonation URL
  7. Note: Web app does NOT currently validate or handle the impersonation token
  8. Session ends automatically after 4 hours or can be manually ended

Current Implementation Limitations

  • No Token Validation: Web app’s /auth-redirect page does not check for or validate impersonation tokens
  • No Active Session: Token is generated but not used to establish an impersonated session
  • Manual Implementation: Currently relies on admin manually logging in as the user
  • Audit Trail Only: System logs intent to impersonate but doesn’t track actual impersonated actions

🔐 Implementation Details

Database Schema

The impersonation_sessions table stores all impersonation sessions:

CREATE TABLE impersonation_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), admin_user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, target_user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, target_user_email TEXT NOT NULL, target_user_name TEXT NOT NULL, reason TEXT NOT NULL, session_token TEXT UNIQUE NOT NULL, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'ended')), started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), ended_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Indexes for performance CREATE INDEX idx_impersonation_sessions_admin_user ON impersonation_sessions(admin_user_id); CREATE INDEX idx_impersonation_sessions_target_user ON impersonation_sessions(target_user_id); CREATE INDEX idx_impersonation_sessions_token ON impersonation_sessions(session_token); CREATE INDEX idx_impersonation_sessions_status ON impersonation_sessions(status, started_at DESC); CREATE INDEX idx_impersonation_sessions_active ON impersonation_sessions(admin_user_id, target_user_id, status) WHERE status = 'active';

TypeScript Interface

interface ImpersonationSession { id: string adminUserId: string targetUserId: string targetUserEmail: string targetUserName: string reason: string startedAt: string endedAt?: string status: 'active' | 'ended' sessionToken: string }

Actual Service Implementation

The actual implementation is simpler than originally documented:

class UserImpersonationService { private supabase = supabaseAdmin async startImpersonation( adminUserId: string, targetUserId: string, reason: string ): Promise<{ success: boolean; sessionToken?: string; error?: string }> { // 1. Verify admin has 'admin' or 'support' role const { data: adminUser } = await this.supabase .from('profiles') .select('id, email, role') .eq('id', adminUserId) .single() if (!adminUser || !['admin', 'support'].includes(adminUser.role)) { throw new Error('Insufficient permissions') } // 2. Get target user details const { data: targetUser } = await this.supabase .from('profiles') .select('id, email, full_name, subscription_tier, subscription_status') .eq('id', targetUserId) .single() if (!targetUser) { throw new Error('Target user not found') } // 3. Check for existing active session const { data: existingSession } = await this.supabase .from('impersonation_sessions') .select('id') .eq('admin_user_id', adminUserId) .eq('target_user_id', targetUserId) .eq('status', 'active') .single() if (existingSession) { throw new Error('Active session already exists') } // 4. Generate simple UUID-based token const sessionToken = crypto.randomUUID() + '-' + Date.now() // 5. Create session record const { data: session, error } = await this.supabase .from('impersonation_sessions') .insert({ admin_user_id: adminUserId, target_user_id: targetUserId, target_user_email: targetUser.email, target_user_name: targetUser.full_name, reason, session_token: sessionToken, status: 'active', started_at: new Date().toISOString(), }) .select() .single() // 6. Log audit event (high severity) await AuditService.logAction({ userId: adminUserId, action: 'user_impersonation_started', resource: 'user', resourceId: targetUserId, details: { targetUserEmail: targetUser.email, targetUserName: targetUser.full_name, reason, sessionToken, }, severity: 'high', ipAddress: '', userAgent: '', }) return { success: true, sessionToken } } async endImpersonation( adminUserId: string, sessionToken: string ): Promise<{ success: boolean; error?: string }> { // Update session to ended const { data: session, error } = await this.supabase .from('impersonation_sessions') .update({ status: 'ended', ended_at: new Date().toISOString(), }) .eq('admin_user_id', adminUserId) .eq('session_token', sessionToken) .eq('status', 'active') .select() .single() if (error) throw error // Log audit event await AuditService.logAction({ userId: adminUserId, action: 'user_impersonation_ended', resource: 'user', resourceId: session.target_user_id, details: { sessionToken, duration: new Date().getTime() - new Date(session.started_at).getTime(), }, severity: 'medium', ipAddress: '', userAgent: '', }) return { success: true } } async validateSession( sessionToken: string ): Promise<{ valid: boolean; session?: ImpersonationSession }> { const { data: session } = await this.supabase .from('impersonation_sessions') .select('*') .eq('session_token', sessionToken) .eq('status', 'active') .single() if (!session) return { valid: false } // Check if session is older than 4 hours const sessionAge = Date.now() - new Date(session.started_at).getTime() const maxAge = 4 * 60 * 60 * 1000 // 4 hours if (sessionAge > maxAge) { await this.endImpersonation(session.admin_user_id, sessionToken) return { valid: false } } return { valid: true, session } } }

Admin Dashboard - Actual Implementation

The admin app provides two pages for user impersonation:

1. Users List Page (/dashboard/users)

// Simplified impersonation from users list const handleImpersonate = async (userId: string) => { try { const token = await userManagementService.createImpersonationToken( userId, 'admin-user' ) if (token) { const mainAppUrl = process.env.NEXT_PUBLIC_MAIN_APP_URL || 'http://localhost:3000' const impersonateUrl = `${mainAppUrl}/auth-redirect?impersonate=${token}` window.open(impersonateUrl, '_blank') } else { alert('Failed to create impersonation token') } } catch (error) { alert('Error starting impersonation') } }

2. Dedicated Impersonation Page (/dashboard/users/impersonate)

  • Provides user search functionality
  • Displays user details (name, email, subscription, status)
  • Shows security warning about audit logging
  • Creates session record in database (via UserImpersonationService)
  • Opens new tab with impersonation URL

Current Token Generation (in userManagementService):

async createImpersonationToken( userId: string, adminUserId: string ): Promise<string | null> { // Simple token - NOT stored or validated const token = `impersonate_${userId}_${Date.now()}` return token }

Important Note: This simplified token generation in userManagementService is different from the full UserImpersonationService implementation. The full service creates database records and audit logs, while the simple service just generates a token string.

Web App Integration - Current State

Status: NOT IMPLEMENTED

The web app’s /auth-redirect page currently:

  • Handles session sharing between admin and web app (for cross-domain auth)
  • Redirects authenticated users to dashboard
  • Does NOT check for or validate impersonation tokens

What’s Missing:

// This code does NOT exist in the web app currently export function ImpersonationBanner() { // No impersonation detection // No session validation // No UI banner for impersonated sessions }

The /auth-redirect page checks for:

  • returnTo parameter (for normal redirects)
  • Popup mode for session sharing
  • User authentication status

But it does NOT check for:

  • impersonate query parameter
  • Impersonation session tokens
  • Active impersonation sessions

To Complete Implementation, Web App Needs:

  1. Check searchParams.get('impersonate') in /auth-redirect
  2. Validate token against impersonation_sessions table
  3. Establish impersonated session (possibly using Supabase admin.getUserById)
  4. Display impersonation banner in layout
  5. Track impersonated actions in audit logs
  6. Provide “End Session” functionality

📊 Audit and Compliance

Actual Audit Implementation

The system uses AuditService to log impersonation events to the audit_logs table:

Audit Log Structure:

interface AuditLog { id: string userId: string // Admin user who initiated impersonation userEmail?: string action: string // 'user_impersonation_started' or 'user_impersonation_ended' resource: string // 'user' resourceId?: string // Target user ID details: Record<string, any> ipAddress?: string userAgent?: string timestamp: string severity: 'low' | 'medium' | 'high' | 'critical' category: 'auth' | 'data' | 'system' | 'security' | 'billing' | 'content' }

Events Logged:

  1. Impersonation Start (High Severity):
await AuditService.logAction({ userId: adminUserId, action: 'user_impersonation_started', resource: 'user', resourceId: targetUserId, details: { targetUserEmail: targetUser.email, targetUserName: targetUser.full_name, reason: reason, sessionToken: sessionToken }, severity: 'high', category: 'security' })
  1. Impersonation End (Medium Severity):
await AuditService.logAction({ userId: adminUserId, action: 'user_impersonation_ended', resource: 'user', resourceId: targetUserId, details: { sessionToken: sessionToken, duration: durationInMs }, severity: 'medium', category: 'security' })

Note: Individual actions performed during impersonation are NOT currently logged, only session start/end events.

Security Monitoring

Current Implementation: Basic session tracking only

The system provides:

  • Session records in impersonation_sessions table
  • Audit logs for session start/end events
  • Simple validation checking for:
    • Active sessions (prevents duplicate sessions)
    • Session age (auto-expires after 4 hours)
    • Admin role verification

Not Implemented:

  • Real-time suspicious activity detection
  • Action-level monitoring during impersonation
  • Rate limiting on impersonation attempts
  • Automated alerts for security violations
  • Pattern detection for abuse

Available Queries:

// Get active sessions for an admin const sessions = await userImpersonationService.getActiveSessions(adminUserId) // Get impersonation history const history = await userImpersonationService.getImpersonationHistory(adminUserId, 50) // Validate session is still active and not expired const { valid, session } = await userImpersonationService.validateSession(token)

🔒 Access Control and Permissions

Actual Role Requirements

Who Can Impersonate:

// Role check in UserImpersonationService.startImpersonation() if (!adminUser || !['admin', 'support'].includes(adminUser.role)) { throw new Error('Insufficient permissions for user impersonation') }

Allowed Roles:

  • admin - Full impersonation access
  • support - Full impersonation access (same as admin)

Session Duration:

  • Fixed: 4 hours maximum
  • No configurable duration options
  • Automatic expiration on validation

Current Limitations:

  • No distinction between admin and support permissions
  • No target role restrictions (can impersonate any user)
  • No action-level restrictions during impersonation
  • No approval workflows
  • No MFA requirement

📱 User Interface

Admin Dashboard Pages

1. Users List (/dashboard/users)

  • Shows all users with filtering (tier, status)
  • Impersonate button on each user row
  • Quick access without requiring reason

2. Impersonation Page (/dashboard/users/impersonate)

  • Dedicated search interface
  • Security warning banner (red)
  • User search by name/email/ID
  • Displays: avatar, name, email, status, tier, campaigns/stories count
  • Instructions on how impersonation works

Features:

  • User avatar with initial
  • Status badges (active, trial, expired, inactive)
  • Subscription tier badges (starter, family, premium)
  • Last login and join date display
  • Loading states during token generation

Security UI Elements:

// Warning banner on impersonation page <div className="bg-red-50 border border-red-200"> <h3>Security Warning</h3> <p> User impersonation is a powerful administrative tool. All impersonation sessions are logged and monitored. Only use this feature for legitimate support and troubleshooting purposes. </p> </div>

Note: No active session monitoring dashboard currently exists.

🚨 Current Security Status

Implemented Security Features

Role-Based Access Control:

  • ✅ Admin and support roles can impersonate
  • ✅ Role verification before session creation
  • ✅ Database constraints on admin/target user relationships

Session Management:

  • ✅ Sessions stored in database
  • ✅ 4-hour automatic expiration
  • ✅ Prevents duplicate active sessions
  • ✅ Session validation on access

Audit Logging:

  • ✅ High-severity logs for session start
  • ✅ Medium-severity logs for session end
  • ✅ Session details (target, reason, duration)
  • ✅ Logs stored in audit_logs table

Security Gaps

Critical:

  • ❌ Web app doesn’t validate or use impersonation tokens
  • ❌ No actual impersonated session established
  • ❌ No impersonation banner in web app UI
  • ❌ No tracking of actions during impersonation

Important:

  • ❌ IP address not captured in audit logs
  • ❌ User agent not captured in audit logs
  • ❌ No MFA requirement for impersonation
  • ❌ No rate limiting on impersonation attempts
  • ❌ No approval workflow for sensitive users

Nice to Have:

  • ❌ No real-time monitoring dashboard
  • ❌ No suspicious activity detection
  • ❌ No automated alerts
  • ❌ No compliance report generation

🔧 Troubleshooting

Known Issues

1. Impersonation Doesn’t Actually Work

  • Token is generated but web app doesn’t use it
  • User must manually log in as themselves
  • This is a partial implementation - session tracking exists but session establishment doesn’t

2. Two Different Token Generation Methods

  • userManagementService.createImpersonationToken() - Simple string generation, no DB record
  • userImpersonationService.startImpersonation() - Full session creation with audit logs
  • The UI pages use the simple method, not the full service

3. Missing IP/User Agent in Audit Logs

  • Audit logs save empty strings for ipAddress and userAgent
  • Would need to be captured from request context

4. Session Expiration

  • Sessions expire after 4 hours
  • Auto-expires on validation but no cleanup job runs
  • Ended sessions remain in database indefinitely

Debugging Queries

// Check impersonation sessions in database const { data } = await supabase .from('impersonation_sessions') .select('*') .eq('status', 'active') // Check audit logs const { data: logs } = await supabase .from('audit_logs') .select('*') .in('action', ['user_impersonation_started', 'user_impersonation_ended']) .order('timestamp', { ascending: false }) // Verify admin role const { data: profile } = await supabase .from('profiles') .select('role') .eq('id', userId) .single()

📈 Required Completions

Critical Implementation Needs

1. Complete Web App Integration

  • Add impersonation token validation in /auth-redirect
  • Establish impersonated session using admin.getUserById
  • Display impersonation banner when session is active
  • Implement “End Session” functionality
  • Store impersonation context in session/localStorage

2. Fix Token Generation Inconsistency

  • Decide between simple tokens (userManagementService) or full sessions (userImpersonationService)
  • Update UI to use the full UserImpersonationService
  • Ensure database session records are created for all impersonations

3. Enhance Audit Logging

  • Capture IP address from request headers
  • Capture user agent from request headers
  • Log individual actions during impersonation (optional)
  • Add session end reason tracking

4. Add Session Management

  • Create cleanup job for expired sessions
  • Add ability to view active sessions dashboard
  • Implement force-end session from admin panel
  • Add session duration configuration

Nice-to-Have Improvements

Security:

  • MFA requirement before impersonation
  • Rate limiting on impersonation attempts
  • Approval workflow for certain users/roles
  • Real-time suspicious activity detection

User Experience:

  • Reason requirement on all impersonation pages (currently optional)
  • Session history view for admins
  • Better error messages and validation
  • Toast notifications instead of alerts

Monitoring:

  • Active sessions monitoring dashboard
  • Impersonation analytics and reports
  • Alert system for unusual patterns
  • Compliance export functionality

📝 Summary

What Works Today

The MyStoryFlow impersonation system is partially implemented:

Admin App (Functional):

  • ✅ UI pages for user search and impersonation initiation
  • ✅ Database schema for session tracking
  • ✅ Audit logging for session start/end events
  • ✅ Role-based access control (admin/support only)
  • ✅ Session expiration (4 hours)
  • ✅ UserImpersonationService with full functionality

Web App (Not Functional):

  • ❌ Does not validate impersonation tokens
  • ❌ Does not establish impersonated sessions
  • ❌ No impersonation banner or UI
  • ❌ No end session functionality

How It Currently Works

  1. Admin navigates to /dashboard/users or /dashboard/users/impersonate
  2. Admin clicks “Impersonate” button
  3. System generates token: impersonate_${userId}_${timestamp}
  4. New tab opens to web app with token in URL
  5. Web app ignores the token and shows normal login page
  6. Admin must manually log in as themselves (impersonation doesn’t actually happen)

What Needs to Happen

To make impersonation functional, the web app needs to:

  1. Check for impersonate query parameter in /auth-redirect
  2. Validate token against impersonation_sessions table
  3. Use Supabase Admin SDK to authenticate as target user
  4. Display impersonation banner showing who is being impersonated
  5. Track session and allow ending it

File Locations

  • Admin Service: /apps/admin-app/src/lib/user-impersonation-service.ts
  • Admin Pages: /apps/admin-app/src/app/dashboard/users/ (page.tsx and impersonate/page.tsx)
  • Audit Service: /apps/admin-app/src/lib/audit-service.ts
  • Database Schema: /apps/admin-app/src/scripts/apply-db-optimizations.ts
  • Web App Auth: /apps/web-app/app/auth-redirect/page.tsx (needs implementation)