Assist_Design/docs/SALESFORCE-PORTAL-SECURITY-GUIDE.md
T. Narantuya ece6821766 Enhance Salesforce order fulfillment process and security measures
- Updated PLESK_DEPLOYMENT.md to include new Salesforce credentials and webhook security configurations.
- Refactored order fulfillment controller to streamline the process and improve readability.
- Introduced EnhancedWebhookSignatureGuard for improved HMAC signature validation and nonce management.
- Updated various documentation files to reflect changes in endpoint naming from `/provision` to `/fulfill` for clarity and consistency.
- Enhanced Redis integration for nonce storage to prevent replay attacks.
- Removed deprecated WebhookSignatureGuard in favor of the new enhanced guard.
2025-09-04 14:17:54 +09:00

14 KiB

Salesforce-to-Portal Security Integration Guide

Overview

This guide outlines secure patterns for Salesforce-to-Portal communication specifically for the order provisioning workflow. Based on your architecture, this focuses on order status updates, not invoice handling.

Order Provisioning Flow

Portal Customer → Places Order → Salesforce Order (Pending Review)
                                        ↓
Salesforce Operator → Reviews → Clicks "Provision in WHMCS" Quick Action
                                        ↓
                    Salesforce → Calls Portal BFF → `/orders/{sfOrderId}/fulfill`
                                        ↓
                    Portal BFF → Provisions in WHMCS → Updates Salesforce Order Status
                                        ↓
                    Portal → Polls Order Status → Shows Customer Updates

1. Secure Order Provisioning Communication

Based on your architecture, the order provisioning flow uses direct HTTPS calls from Salesforce to your portal BFF. Here's how to secure this:

Salesforce → Portal BFF Flow:

  1. Salesforce Quick Action calls POST /orders/{sfOrderId}/fulfill
  2. Portal BFF processes the provisioning request
  3. Optional: Portal → Salesforce status updates via webhook

Secure Salesforce Quick Action Setup

In Salesforce:

  1. Named Credential Configuration
<!-- Named Credential: Portal_BFF -->
<NamedCredential>
    <fullName>Portal_BFF</fullName>
    <endpoint>https://your-portal-api.com</endpoint>
    <principalType>Anonymous</principalType>
    <protocol>HttpsOnly</protocol>
    <generateAuthorizationHeader>false</generateAuthorizationHeader>
</NamedCredential>
  1. Apex Class for Secure Webhook Calls
public class PortalWebhookService {
    private static final String WEBHOOK_SECRET = '{!$Credential.Portal_Webhook.Password}';
    
    @future(callout=true)
    public static void provisionOrder(String orderId) {
        try {
            // Prepare secure payload
            Map<String, Object> payload = new Map<String, Object>{
                'orderId' => orderId,
                'timestamp' => System.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''),
                'nonce' => generateNonce()
            };
            
            // Create HMAC signature
            String jsonPayload = JSON.serialize(payload);
            String signature = generateHMACSignature(jsonPayload, WEBHOOK_SECRET);
            
            // Make secure HTTP call
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:Portal_BFF/orders/' + orderId + '/fulfill');
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setHeader('X-SF-Signature', signature);
            req.setHeader('X-SF-Timestamp', payload.get('timestamp').toString());
            req.setHeader('X-SF-Nonce', payload.get('nonce').toString());
            req.setHeader('Idempotency-Key', generateIdempotencyKey(orderId));
            req.setBody(jsonPayload);
            req.setTimeout(30000); // 30 second timeout
            
            Http http = new Http();
            HttpResponse res = http.send(req);
            
            // Handle response
            handleProvisioningResponse(orderId, res);
            
        } catch (Exception e) {
            // Log error and update order status
            System.debug('Provisioning failed for order ' + orderId + ': ' + e.getMessage());
            updateOrderProvisioningStatus(orderId, 'Failed', e.getMessage());
        }
    }
    
    private static String generateHMACSignature(String data, String key) {
        Blob hmacData = Crypto.generateMac('HmacSHA256', Blob.valueOf(data), Blob.valueOf(key));
        return EncodingUtil.convertToHex(hmacData);
    }
    
    private static String generateNonce() {
        return EncodingUtil.convertToHex(Crypto.generateAesKey(128)).substring(0, 16);
    }
    
    private static String generateIdempotencyKey(String orderId) {
        return 'provision_' + orderId + '_' + System.now().getTime();
    }
}

### Optional: Portal → Salesforce Status Updates

If you want the portal to send status updates back to Salesforce (e.g., when provisioning completes), you can set up a reverse webhook:

**Portal BFF → Salesforce Webhook Endpoint:**

```typescript
// In your Portal BFF
export class SalesforceStatusUpdateService {
  async updateOrderStatus(orderId: string, status: string, details?: any) {
    const payload = {
      orderId,
      status,
      timestamp: new Date().toISOString(),
      details: this.sanitizeDetails(details)
    };
    
    // Send to Salesforce webhook endpoint
    await this.sendToSalesforce('/webhook/order-status', payload);
  }
}

2. Portal BFF Security Implementation

Enhanced Order Provisioning Endpoint

Your portal BFF should implement the /orders/{sfOrderId}/fulfill endpoint with these security measures:

// Enhanced order fulfillment endpoint
@Post('orders/:sfOrderId/fulfill')
@UseGuards(EnhancedWebhookSignatureGuard)
async fulfillOrder(
  @Param('sfOrderId') sfOrderId: string,
  @Body() payload: ProvisionOrderRequest,
  @Headers('idempotency-key') idempotencyKey: string
) {
  // Your existing fulfillment logic
  return await this.orderFulfillmentService.fulfillOrder(sfOrderId, payload, idempotencyKey);
}

Enhanced Webhook Security Implementation:

@Injectable()
export class EnhancedWebhookSignatureGuard implements CanActivate {
  constructor(private configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<Request>();
    
    // 1. Verify HMAC signature (existing)
    this.verifyHmacSignature(request);
    
    // 2. Verify timestamp (prevent replay attacks)
    this.verifyTimestamp(request);
    
    // 3. Verify nonce (prevent duplicate processing)
    this.verifyNonce(request);
    
    // 4. Verify source IP (if using IP allowlisting)
    this.verifySourceIp(request);
    
    return true;
  }

  private verifyTimestamp(request: Request): void {
    const timestamp = request.headers['x-sf-timestamp'] as string;
    if (!timestamp) {
      throw new UnauthorizedException('Timestamp required');
    }
    
    const requestTime = new Date(timestamp).getTime();
    const now = Date.now();
    const maxAge = 5 * 60 * 1000; // 5 minutes
    
    if (Math.abs(now - requestTime) > maxAge) {
      throw new UnauthorizedException('Request too old');
    }
  }

  private verifyNonce(request: Request): void {
    const nonce = request.headers['x-sf-nonce'] as string;
    if (!nonce) {
      throw new UnauthorizedException('Nonce required');
    }
    
    // Check if nonce was already used (implement nonce store)
    // This prevents replay attacks
  }
}

2. Outbound Security: Portal → Salesforce

Current Implementation (Already Secure )

Your existing JWT-based authentication is excellent:

// Your current pattern in salesforce-connection.service.ts
// Uses private key JWT authentication - industry standard

Enhanced Patterns for Sensitive Operations

For highly sensitive operations, consider adding:

@Injectable()
export class SecureSalesforceService {
  async createSensitiveRecord(data: SensitiveData, idempotencyKey: string) {
    // 1. Encrypt sensitive fields before sending
    const encryptedData = this.encryptSensitiveFields(data);
    
    // 2. Add idempotency protection
    const headers = {
      'Idempotency-Key': idempotencyKey,
      'X-Request-ID': uuidv4(),
    };
    
    // 3. Use your existing secure connection
    return await this.salesforceConnection.create(encryptedData, headers);
  }

  private encryptSensitiveFields(data: any): any {
    // Encrypt PII fields before transmission
    const sensitiveFields = ['ssn', 'creditCard', 'personalId'];
    // Implementation depends on your encryption strategy
  }
}

3. Data Protection Guidelines

Sensitive Data Handling

// Example: Secure order processing
export class SecureOrderService {
  async processOrderApproval(orderData: OrderApprovalData) {
    // 1. Validate customer permissions
    await this.validateCustomerAccess(orderData.customerNumber);
    
    // 2. Sanitize data for logging
    const sanitizedData = this.sanitizeForLogging(orderData);
    this.logger.log('Processing order approval', sanitizedData);
    
    // 3. Process with minimal data exposure
    const result = await this.processOrder(orderData);
    
    // 4. Audit trail without sensitive data
    await this.createAuditLog({
      action: 'order_approved',
      customerNumber: orderData.customerNumber,
      orderId: orderData.orderId,
      timestamp: new Date(),
      // No sensitive payment or personal data
    });
    
    return result;
  }

  private sanitizeForLogging(data: any): any {
    // Remove or mask sensitive fields for logging
    const { creditCard, ssn, ...safeData } = data;
    return {
      ...safeData,
      creditCard: creditCard ? '****' + creditCard.slice(-4) : undefined,
      ssn: ssn ? '***-**-' + ssn.slice(-4) : undefined,
    };
  }
}

Field-Level Security

// Implement field-level encryption for highly sensitive data
export class FieldEncryptionService {
  private readonly algorithm = 'aes-256-gcm';
  private readonly keyDerivation = 'pbkdf2';

  async encryptField(value: string, fieldType: string): Promise<EncryptedField> {
    const key = await this.deriveKey(fieldType);
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipher(this.algorithm, key);
    
    let encrypted = cipher.update(value, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    return {
      value: encrypted,
      iv: iv.toString('hex'),
      tag: cipher.getAuthTag().toString('hex'),
    };
  }

  async decryptField(encryptedField: EncryptedField, fieldType: string): Promise<string> {
    const key = await this.deriveKey(fieldType);
    const decipher = crypto.createDecipher(this.algorithm, key);
    
    decipher.setAuthTag(Buffer.from(encryptedField.tag, 'hex'));
    
    let decrypted = decipher.update(encryptedField.value, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
  }
}

4. Implementation Checklist

Salesforce Setup

  • Create Platform Events for portal notifications
  • Set up Named Credentials for portal webhook calls
  • Configure IP allowlisting for portal endpoints
  • Implement HMAC signing in Apex
  • Create audit trails for all portal communications

Portal Setup

  • Enhance webhook signature verification
  • Implement timestamp and nonce validation
  • Add IP allowlisting for Salesforce
  • Create encrypted payload handling
  • Implement idempotency protection

Security Measures

  • Rotate webhook secrets regularly
  • Monitor for suspicious webhook activity
  • Implement rate limiting per customer
  • Add comprehensive audit logging
  • Test disaster recovery procedures

5. Monitoring and Alerting

@Injectable()
export class SecurityMonitoringService {
  async monitorWebhookSecurity(request: Request, response: any) {
    const metrics = {
      sourceIp: request.ip,
      userAgent: request.headers['user-agent'],
      timestamp: new Date(),
      success: response.success,
      processingTime: response.processingTime,
    };

    // Alert on suspicious patterns
    if (this.detectSuspiciousActivity(metrics)) {
      await this.sendSecurityAlert(metrics);
    }

    // Log for audit
    this.logger.log('Webhook security metrics', metrics);
  }

  private detectSuspiciousActivity(metrics: any): boolean {
    // Implement your security detection logic
    // - Too many requests from same IP
    // - Unusual timing patterns
    // - Failed authentication attempts
    return false;
  }
}

6. Testing Security

describe('Webhook Security', () => {
  it('should reject webhooks without valid HMAC signature', async () => {
    const invalidPayload = { data: 'test' };
    const response = await request(app)
      .post('/webhooks/salesforce')
      .send(invalidPayload)
      .expect(401);
    
    expect(response.body.message).toContain('Invalid webhook signature');
  });

  it('should reject old timestamps', async () => {
    const oldTimestamp = new Date(Date.now() - 10 * 60 * 1000); // 10 minutes ago
    const payload = { data: 'test' };
    const signature = generateHmacSignature(payload);
    
    const response = await request(app)
      .post('/webhooks/salesforce')
      .set('X-SF-Signature', signature)
      .set('X-SF-Timestamp', oldTimestamp.toISOString())
      .send(payload)
      .expect(401);
  });
});

7. Production Deployment

Environment Variables

# Webhook Security
SF_WEBHOOK_SECRET=your_256_bit_secret_key_here
SF_WEBHOOK_IP_ALLOWLIST=13.108.0.0/14,204.14.232.0/23
WEBHOOK_TIMESTAMP_TOLERANCE=300000  # 5 minutes in ms

# Encryption
FIELD_ENCRYPTION_KEY=your_field_encryption_master_key
ENCRYPTION_KEY_ROTATION_DAYS=90

# Monitoring
SECURITY_ALERT_WEBHOOK=https://your-monitoring-service.com/alerts
AUDIT_LOG_RETENTION_DAYS=2555  # 7 years for compliance

Salesforce Named Credential Setup

<!-- Named Credential: Portal_Webhook -->
<NamedCredential>
    <fullName>Portal_Webhook</fullName>
    <endpoint>https://your-portal-api.com</endpoint>
    <principalType>Anonymous</principalType>
    <protocol>HttpsOnly</protocol>
    <generateAuthorizationHeader>false</generateAuthorizationHeader>
</NamedCredential>

This guide provides a comprehensive, production-ready approach to secure Salesforce-Portal integration that builds on your existing security infrastructure while adding enterprise-grade protection for sensitive data transmission.