Assist_Design/docs/SALESFORCE-PORTAL-SECURITY-GUIDE.md

437 lines
14 KiB
Markdown
Raw Normal View History

# 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
### Primary Method: Direct HTTPS Webhook (Recommended for Order Flow)
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**
```xml
<!-- 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>
```
2. **Apex Class for Secure Webhook Calls**
```apex
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:
```typescript
// 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:**
```typescript
@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:
```typescript
// 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:
```typescript
@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
```typescript
// 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
```typescript
// 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
```typescript
@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
```typescript
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
```bash
# 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
```xml
<!-- 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.