critical: Confirm plugin must know when activeDid changes
- Added critical requirement that plugin MUST be notified of activeDid changes - Enhanced plugin interface with onActiveDidChange() callback method - Added clearCacheForNewIdentity() and refreshAuthenticationForNewIdentity() methods - Updated integration examples to include activeDid change listeners - Created comprehensive ActiveDid change requirements document covering: * Security implications of not detecting changes * Event-based notification pattern implementation * Cache clearing and authentication refresh requirements * Testing scenarios for identity switching * Platform-specific considerations and edge cases * Performance optimization for rapid identity changes This addresses the critical data integrity and security requirement that the plugin must know when TimeSafari users switch identities to prevent data leakage.
This commit is contained in:
204
doc/ACTIVE_DID_CHANGE_REQUIREMENTS.md
Normal file
204
doc/ACTIVE_DID_CHANGE_REQUIREMENTS.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# ActiveDid Change Requirements
|
||||||
|
|
||||||
|
**Author**: Matthew Raymer
|
||||||
|
**Version**: 1.0.0
|
||||||
|
**Created**: 2025-10-02 12:00:00 UTC
|
||||||
|
**Last Updated**: 2025-10-02 12:00:00 UTC
|
||||||
|
|
||||||
|
## Critical Requirement: Plugin Must Know When activeDid Changes
|
||||||
|
|
||||||
|
**CONFIRMED**: The plugin **must** be notified when activeDid changes to maintain data integrity and security.
|
||||||
|
|
||||||
|
## Why This Is Critical
|
||||||
|
|
||||||
|
### **Security Implications**
|
||||||
|
- **Authentication Isolation**: Each activeDid has different authentication tokens
|
||||||
|
- **Data Privacy**: Cached content belongs to specific user identity
|
||||||
|
- **Authorization**: API calls must use correct activeDid for proper authorization
|
||||||
|
|
||||||
|
### **Data Integrity Issues**
|
||||||
|
- **Cache Pollution**: Wrong user's data could leak between identities
|
||||||
|
- **Stale Authentication**: Old JWT tokens become invalid for new identity
|
||||||
|
- **Background Tasks**: Tasks running with wrong identity context
|
||||||
|
|
||||||
|
### **User Experience Problems**
|
||||||
|
```typescript
|
||||||
|
// PROBLEMATIC SCENARIO WITHOUT CHANGE NOTIFICATION:
|
||||||
|
// 1. User A schedules notification for "my offers"
|
||||||
|
// 2. User switches to User B
|
||||||
|
// 3. Plugin doesn't know about change
|
||||||
|
// 4. Plugin delivers User A's personal offer data to User B ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solution: Event-Based Change Notification
|
||||||
|
|
||||||
|
### **TimeSafari Host Application**
|
||||||
|
```typescript
|
||||||
|
// In PlatformServiceMixin.ts - CRITICAL PATTERN
|
||||||
|
async $updateActiveDid(newDid: string | null): Promise<void> {
|
||||||
|
// ... existing activeDid update logic ...
|
||||||
|
|
||||||
|
// MUST notify Daily Notification Plugin
|
||||||
|
await this.$notifyDailyNotificationService(newDid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $notifyDailyNotificationService(newActiveDid: string): Promise<void> {
|
||||||
|
const event = new CustomEvent('activeDidChanged', {
|
||||||
|
detail: { activeDid: newActiveDid }
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Plugin Side Implementation**
|
||||||
|
```typescript
|
||||||
|
// In Daily Notification Plugin - MANDATORY LISTENING
|
||||||
|
export class EnhancedDailyNotificationPlugin {
|
||||||
|
private currentActiveDid: string | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setupActiveDidChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupActiveDidChangeListener(): void {
|
||||||
|
document.addEventListener('activeDidChanged', (event: CustomEvent) => {
|
||||||
|
this.handleActiveDidChange(event.detail.activeDid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleActiveDidChange(newActiveDid: string): Promise<void> {
|
||||||
|
if (newActiveDid !== this.currentActiveDid) {
|
||||||
|
logger.info(`[Plugin] ActiveDid changing from ${this.currentActiveDid} to ${newActiveDid}`);
|
||||||
|
|
||||||
|
// CRITICAL ACTIONS REQUIRED:
|
||||||
|
await this.clearCacheForNewIdentity();
|
||||||
|
await this.refreshAuthenticationForNewIdentity(newActiveDid);
|
||||||
|
await this.updateBackgroundTaskIdentity(newActiveDid);
|
||||||
|
|
||||||
|
this.currentActiveDid = newActiveDid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearCacheForNewIdentity(): Promise<void> {
|
||||||
|
// Clear all cached content to prevent data leakage
|
||||||
|
await this.clearStoredContent();
|
||||||
|
await this.clearAuthenticationTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshAuthenticationForNewIdentity(activeDid: string): Promise<void> {
|
||||||
|
// Generate new JWT tokens with correct activeDid
|
||||||
|
const newJwt = await this.generateJWT(activeDid);
|
||||||
|
this.currentAuthToken = newJwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateBackgroundTaskIdentity(activeDid: string): Promise<void> {
|
||||||
|
// Update background tasks to use new identity
|
||||||
|
await this.pauseBackgroundTasks();
|
||||||
|
await this.configureBackgroundTasksForIdentity(activeDid);
|
||||||
|
await this.resumeBackgroundTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Requirements
|
||||||
|
|
||||||
|
### **Host Application Responsibilities**
|
||||||
|
1. **Event Dispatch**: Must dispatch `activeDidChanged` events
|
||||||
|
2. **Timing**: Dispatch immediately after updating active_identity table
|
||||||
|
3. **Reliability**: Event must reach all plugin listeners
|
||||||
|
|
||||||
|
### **Plugin Responsibilities**
|
||||||
|
1. **Event Listening**: Must listen for `activeDidChanged` events
|
||||||
|
2. **Cache Clearing**: Must clear all cached content for new identity
|
||||||
|
3. **Authentication Refresh**: Must generate new tokens with updated activeDid
|
||||||
|
4. **Background Task Update**: Must restart background tasks with new context
|
||||||
|
5. **Error Handling**: Must handle failures gracefully during identity transition
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
### **Integration Tests**
|
||||||
|
```typescript
|
||||||
|
describe('ActiveDid Change Handling', () => {
|
||||||
|
it('should clear cache when activeDid changes', async () => {
|
||||||
|
// Set up initial identity
|
||||||
|
await plugin.setActiveDidFromHost('did:alice:123');
|
||||||
|
await plugin.cacheContentData({ /* Alice's data */ });
|
||||||
|
|
||||||
|
// Simulate identity change
|
||||||
|
const event = new CustomEvent('activeDidChanged', {
|
||||||
|
detail: { activeDid: 'did:bob:456' }
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Verify cache is cleared
|
||||||
|
expect(await plugin.getCachedContent()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should refresh authentication tokens', async () => {
|
||||||
|
// Set initial identity
|
||||||
|
const aliceToken = await plugin.generateJWT('did:alice:123');
|
||||||
|
|
||||||
|
// Change identity
|
||||||
|
document.dispatchEvent(new CustomEvent('activeDidChanged', {
|
||||||
|
detail: { activeDid: 'did:bob:456' }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Verify new token with correct identity
|
||||||
|
const bobToken = await plugin.getCurrentAuthToken();
|
||||||
|
expect(bobToken.payload.sub).toBe('did:bob:456');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple rapid identity changes', async () => {
|
||||||
|
// Test rapid switching between identities
|
||||||
|
const identities = ['did:alice:123', 'did:bob:456', 'did:alice:123'];
|
||||||
|
|
||||||
|
for (const identity of identities) {
|
||||||
|
document.dispatchEvent(new CustomEvent('activeDidChanged', {
|
||||||
|
detail: { activeDid: identity }
|
||||||
|
}));
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100)); // Brief pause
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure plugin ends up in correct state
|
||||||
|
expect(plugin.currentActiveDid).toBe('did:alice:123');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Edge Case Testing**
|
||||||
|
- **Network Failure**: Identity changes during network failures
|
||||||
|
- **Background Mode**: Identity changes when app is backgrounded
|
||||||
|
- **Rapid Switches**: Multiple identity changes in quick succession
|
||||||
|
- **Invalid activeDid**: Change to empty or invalid activeDid
|
||||||
|
|
||||||
|
## Platform-Specific Considerations
|
||||||
|
|
||||||
|
### **Android/Electron**
|
||||||
|
- Plugin can detect activeDid changes in background via database polling
|
||||||
|
- Backup mechanism if event-based notification fails
|
||||||
|
|
||||||
|
### **Web**
|
||||||
|
- Event-based notification is primary mechanism
|
||||||
|
- Fallback to periodic activeDid checking
|
||||||
|
|
||||||
|
### **iOS**
|
||||||
|
- Background tasks can check activeDid changes
|
||||||
|
- Event-based notification for foreground changes
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### **Change Detection Optimization**
|
||||||
|
- Debounce rapid identity changes
|
||||||
|
- Batch cache clearing operations
|
||||||
|
- Minimize background task restarts
|
||||||
|
|
||||||
|
### **Resource Cleanup**
|
||||||
|
- Clear authentication tokens immediately
|
||||||
|
- Clean up cached content efficiently
|
||||||
|
- Avoid memory leaks during transitions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Critical requirement documented - Implementation required
|
||||||
|
**Next Steps**: Add activeDid change listeners to plugin implementation
|
||||||
|
**Security Impact**: HIGH - Prevents data leakage between user identities
|
||||||
@@ -271,11 +271,15 @@ await plugin.configureDatabase({
|
|||||||
const activeIdentity = await this.$getActiveIdentity();
|
const activeIdentity = await this.$getActiveIdentity();
|
||||||
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
|
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
|
||||||
|
|
||||||
|
// CRITICAL: Set up activeDid change listener
|
||||||
|
plugin.onActiveDidChange(async (newActiveDid) => {
|
||||||
|
await plugin.clearCacheForNewIdentity();
|
||||||
|
await plugin.refreshAuthenticationForNewIdentity(newActiveDid);
|
||||||
|
logger.info(`[TimeSafari] ActiveDid changed to: ${newActiveDid}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Enable automatic background lookup
|
// Enable automatic background lookup
|
||||||
await plugin.enableAutoActiveDidMode();
|
await plugin.enableAutoActiveDidMode();
|
||||||
|
|
||||||
// Plugin can fetch content and cache independently
|
|
||||||
const results = await plugin.executeContentFetch(config);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Web: Host-Managed Database with Hybrid activeDid**
|
**Web: Host-Managed Database with Hybrid activeDid**
|
||||||
@@ -345,6 +349,11 @@ interface EnhancedDailyNotificationPlugin {
|
|||||||
// TimeSafari Integration Methods
|
// TimeSafari Integration Methods
|
||||||
initializeFromTimeSafari(): Promise<void>;
|
initializeFromTimeSafari(): Promise<void>;
|
||||||
listenForActiveDidChanges(): Promise<void>;
|
listenForActiveDidChanges(): Promise<void>;
|
||||||
|
|
||||||
|
// Critical: ActiveDid Change Handling
|
||||||
|
onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): void;
|
||||||
|
clearCacheForNewIdentity(): Promise<void>;
|
||||||
|
refreshAuthenticationForNewIdentity(activeDid: string): Promise<void>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -356,6 +365,11 @@ interface EnhancedDailyNotificationPlugin {
|
|||||||
- **Implement** dual-mode activeDid access:
|
- **Implement** dual-mode activeDid access:
|
||||||
- **Foreground**: Host provides activeDid via `setActiveDidFromHost()`
|
- **Foreground**: Host provides activeDid via `setActiveDidFromHost()`
|
||||||
- **Background**: Plugin looks up activeDid via `refreshActiveDidFromDatabase()`
|
- **Background**: Plugin looks up activeDid via `refreshActiveDidFromDatabase()`
|
||||||
|
- **Critical**: Plugin **MUST** know when activeDid changes for:
|
||||||
|
- **Event-Based Notification**: Listen for `activeDidChanged` events from TimeSafari
|
||||||
|
- **Cache Invalidation**: Clear cached content when user switches identity
|
||||||
|
- **Token Refresh**: Generate new JWT tokens with updated activeDid
|
||||||
|
- **Background Task Coordination**: Stop/restart tasks with new identity context
|
||||||
- **Coordinate** with TimeSafari's PlatformServiceMixin for identity changes
|
- **Coordinate** with TimeSafari's PlatformServiceMixin for identity changes
|
||||||
|
|
||||||
## Migration & Testing Strategy
|
## Migration & Testing Strategy
|
||||||
@@ -375,6 +389,8 @@ interface EnhancedDailyNotificationPlugin {
|
|||||||
- Test `setActiveDidFromHost()` with TimeSafari PlatformServiceMixin
|
- Test `setActiveDidFromHost()` with TimeSafari PlatformServiceMixin
|
||||||
- Test `refreshActiveDidFromDatabase()` with background tasks
|
- Test `refreshActiveDidFromDatabase()` with background tasks
|
||||||
- Test `enableAutoActiveDidMode()` for automatic synchronization
|
- Test `enableAutoActiveDidMode()` for automatic synchronization
|
||||||
|
- **Critical**: Test `onActiveDidChange()` listener with identity switches
|
||||||
|
- Test cache invalidation and token refresh during activeDid changes
|
||||||
- **Background testing** on real devices (doze mode, app backgrounding)
|
- **Background testing** on real devices (doze mode, app backgrounding)
|
||||||
- **Authentication testing** with actual DID credentials from TimeSafari active_identity table
|
- **Authentication testing** with actual DID credentials from TimeSafari active_identity table
|
||||||
- **Cross-platform testing** for Android/Electron (SQLite access) vs Web (host delegation) patterns
|
- **Cross-platform testing** for Android/Electron (SQLite access) vs Web (host delegation) patterns
|
||||||
|
|||||||
Reference in New Issue
Block a user