keys-for-all/docs/VUKeyServerIntegration.md

469 lines
18 KiB
Markdown
Raw Permalink Normal View History

2025-07-22 18:27:21 -07:00
# VUKeyServerIntegration Component
## Overview
The VUKeyServerIntegration component handles license key retrieval and syncing for VoiceUwu in an **offline-first** architecture. The app functions completely offline, with the keyserver only used for purchasing and retrieving keys associated with the user's account.
## Architecture (Offline-First)
```
┌─────────────────────────────────┐ ┌─────────────────┐ ┌─────────────────────────────────┐
│ iOS App │ │ Your Server │ │ Android App │
│ (Offline-First) │ │ (KeyServer) │ │ (Offline-First) │
│ │ │ │ │ │
│ ┌─────────────────────────────┐ │ │ │ │ ┌─────────────────────────────┐ │
│ │ Local Key Storage │ │ │ │ │ │ Local Key Storage │ │
│ │ - Encrypted in Keychain │ │ │ │ │ │ - Encrypted locally │ │
│ │ - Full offline validation │ │ │ │ │ │ - Full offline validation │ │
│ │ - No network required │ │ │ │ │ │ - No network required │ │
│ └─────────────────────────────┘ │ │ │ │ └─────────────────────────────┘ │
│ │ │ │ │ │
│ ┌─────────────────────────────┐ │ │ │ │ ┌─────────────────────────────┐ │
│ │ Keys for All Feature │ │ │ │ │ │ Keys for All Feature │ │
│ │ - Auto-check on open │ │ │ │ │ │ - Auto-check on open │ │
│ │ - Manual sync button │ │ │ │ │ │ - Manual sync button │ │
│ │ - Shows available keys │ │ │ │ │ │ - Shows available keys │ │
│ └─────────────────────────────┘ │ │ │ │ └─────────────────────────────┘ │
└─────────────────────────────────┘ └─────────────────┘ └─────────────────────────────────┘
│ │ │
│ Sync: Get user's keys │ │
│──────────────────────────────→│ │
│ │ │
│ Response: [key1, key2, ...] │ │
│←──────────────────────────────│ │
│ │ │
│ │ Sync: Get user's keys │
│ │←──────────────────────────────│
│ │ │
│ │ Response: [key1, key2, ...] │
│ │──────────────────────────────→│
```
## Components
### 1. VUKeyServerClient
**Purpose**: Client for keyserver communication (purchase, wallet management, feature unlocking)
**Platform**: Cross-platform interface supporting multiple apps
```swift
protocol VUKeyServerClient {
// Multi-platform key pack purchasing
func purchaseKeyPack(pack: KeyPack, via platform: Platform) async throws -> KeyPurchaseResult
// Wallet management
func getWalletBalance(userId: String) async throws -> Int
func spendKeys(amount: Int, feature: String, app: String, userId: String) async throws -> SpendResult
func syncWallet(userId: String) async throws -> WalletState
// App-specific features
func getAppFeatures(appId: String) async throws -> [AppFeature]
func unlockFeature(featureId: String, appId: String, userId: String) async throws -> UnlockResult
func getUserFeatures(userId: String, appId: String) async throws -> [String]
// Community features
func donateKeys(amount: Int, userId: String) async throws -> DonationResult
func shareKeys(amount: Int, recipientEmail: String, userId: String) async throws -> ShareResult
}
enum Platform {
case apple, google, stripe, paypal, steam
}
struct KeyPurchaseResult {
let keysAdded: Int
let bonusKeys: Int
let communityDonated: Int
let transactionId: String
}
struct SpendResult {
let remainingBalance: Int
let featureUnlocked: Bool
let transactionId: String
}
```
### 2. Platform-Specific Purchase Managers
#### VUAppleStoreKitManager
**Purpose**: Handle Apple App Store purchases (iOS/macOS)
**Platform**: iOS/macOS
```swift
class VUAppleStoreKitManager {
func purchaseProduct(_ productId: String) async throws -> StoreKit.Transaction
func restorePurchases() async throws -> [StoreKit.Transaction]
func sendTransactionToKeyServer(_ transaction: StoreKit.Transaction) async throws -> [VULicenseKey]
}
```
#### VUGooglePlayManager
**Purpose**: Handle Google Play purchases (Android)
**Platform**: Android
```swift
class VUGooglePlayManager {
func purchaseProduct(_ productId: String) async throws -> GooglePlayPurchase
func sendPurchaseToKeyServer(_ purchase: GooglePlayPurchase) async throws -> [VULicenseKey]
}
```
#### VUStripeManager
**Purpose**: Handle Stripe payments (Web/Direct)
**Platform**: Web/Cross-platform
```swift
class VUStripeManager {
func createPaymentIntent(amount: Int, email: String) async throws -> String
func confirmPaymentAndGetKeys(_ paymentIntentId: String) async throws -> [VULicenseKey]
}
```
#### VUPayPalManager
**Purpose**: Handle PayPal payments (Web/Direct)
**Platform**: Web/Cross-platform
```swift
class VUPayPalManager {
func createPayment(amount: Double, email: String) async throws -> String
func executePaymentAndGetKeys(_ paymentId: String) async throws -> [VULicenseKey]
}
```
#### VUSteamManager
**Purpose**: Handle Steam purchases (PC Gaming)
**Platform**: PC/Steam
```swift
class VUSteamManager {
func initiateSteamPurchase(_ productId: String) async throws -> String
func validateSteamPurchaseAndGetKeys(_ transactionId: String) async throws -> [VULicenseKey]
}
```
### 3. VUKeysForAllSync
**Purpose**: Handles the "Keys for All" feature sync functionality
**Platform**: iOS/Android
```swift
class VUKeysForAllSync {
private let keyServerClient: VUKeyServerClient
private let keyStorage: VUKeyStorage
// Auto-sync when feature is opened
func autoSyncOnOpen() async throws -> [VULicenseKey]
// Manual sync via button
func manualSync() async throws -> [VULicenseKey]
// Check if user has account/email for syncing
func canSync() -> Bool
// Store retrieved keys locally
func storeRetrievedKeys(_ keys: [VULicenseKey]) async throws
}
```
## Data Models
### VULicenseKey
```swift
struct VULicenseKey: Codable {
let keyValue: String // The actual license key (e.g., "VUUW-ABCD-1234-WXYZ-L2")
let planType: VUPlanType // Level 1, Level 2, etc.
let purchaseDate: Date // When it was purchased
let serverMetadata: [String: Any]? // Optional server data
}
```
### VUUserKeySync
```swift
struct VUUserKeySync: Codable {
let userEmail: String
let availableKeys: [VULicenseKey]
let syncTimestamp: Date
}
```
## Integration Flows
### Purchase Flow (Apple - iOS/macOS)
1. User initiates purchase in iOS/macOS app
2. StoreKit 2 handles payment with Apple
3. App receives JWS-signed transaction
4. App sends transaction to keyserver via `purchaseWithApple()`
5. Keyserver validates JWS signature and generates keys
6. Keyserver returns generated keys to app
7. App stores keys locally using KeyStorage
### Purchase Flow (Google Play - Android)
1. User initiates purchase in Android app
2. Google Play Billing handles payment
3. App receives purchase token
4. App sends purchase token to keyserver via `purchaseWithGoogle()`
5. Keyserver validates with Google Play API and generates keys
6. Keyserver returns generated keys to app
7. App stores keys locally
### Purchase Flow (Stripe - Web/Direct)
1. User completes payment on website/web app
2. Stripe processes payment and returns payment intent ID
3. Client calls `purchaseWithStripe()` with payment intent and email
4. Keyserver validates with Stripe API and generates keys
5. Keyserver returns generated keys
6. Keys stored locally in app
### Purchase Flow (PayPal - Web/Direct)
1. User completes payment via PayPal
2. PayPal processes payment and returns payment ID
3. Client calls `purchaseWithPayPal()` with payment ID and email
4. Keyserver validates with PayPal API and generates keys
5. Keyserver returns generated keys
6. Keys stored locally in app
### Purchase Flow (Steam - PC Gaming)
1. User purchases through Steam store
2. Steam processes payment and provides transaction ID
3. Steam client calls `purchaseWithSteam()` with Steam ID and transaction
4. Keyserver validates with Steam Web API and generates keys
5. Keyserver returns generated keys
6. Keys stored locally in Steam client/game
### Keys for All Feature Flow
1. User opens "Keys for All" feature
2. App calls `autoSyncOnOpen()` which:
- Checks if user has email/account configured
- Calls `syncUserKeys()` if available
- Compares server keys with local keys
- Stores any new keys locally
3. User can tap "Sync" button to trigger `manualSync()`
4. All keys stored locally work offline immediately
## Server-Side Integration
### Endpoints
```
# Key pack purchase endpoints
POST /api/v1/keys/purchase/apple # Process Apple key pack purchase
POST /api/v1/keys/purchase/google # Process Google Play key pack purchase
POST /api/v1/keys/purchase/stripe # Process Stripe key pack payment
POST /api/v1/keys/purchase/paypal # Process PayPal key pack payment
POST /api/v1/keys/purchase/steam # Process Steam key pack purchase
# Wallet management endpoints
GET /api/v1/wallet/:userId # Get user's key wallet balance
POST /api/v1/wallet/spend # Spend keys on app feature
POST /api/v1/wallet/sync # Sync wallet across devices
GET /api/v1/wallet/history/:userId # Get spending transaction history
# App feature endpoints
GET /api/v1/apps/:appId/features # Get app's feature definitions
POST /api/v1/apps/:appId/unlock # Unlock feature in specific app
GET /api/v1/apps/:appId/unlocked/:userId # Get user's unlocked features
# Community endpoints
POST /api/v1/community/donate # Donate keys to community pool
POST /api/v1/community/share # Share keys with another user
GET /api/v1/community/pool # Get community pool status
```
### Platform Validation Examples
```javascript
// Apple JWS Validation
const jwt = require('jsonwebtoken');
const appleRootCert = loadAppleRootCertificate();
function validateAppleTransaction(jws) {
try {
const decoded = jwt.verify(jws, appleRootCert, {
algorithms: ['ES256'],
issuer: 'https://appleid.apple.com'
});
return decoded;
} catch (error) {
throw new Error('Invalid Apple transaction');
}
}
// Google Play Validation
async function validateGooglePlayPurchase(purchaseToken, productId) {
const response = await googlePlayAPI.purchases.products.get({
packageName: 'com.uwuapps.voiceuwu',
productId: productId,
token: purchaseToken
});
return response.data;
}
// Stripe Validation
async function validateStripePayment(paymentIntentId) {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
if (paymentIntent.status !== 'succeeded') {
throw new Error('Payment not completed');
}
return paymentIntent;
}
// PayPal Validation
async function validatePayPalPayment(paymentId) {
const payment = await paypal.payment.get(paymentId);
if (payment.state !== 'approved') {
throw new Error('Payment not approved');
}
return payment;
}
// Steam Validation
async function validateSteamPurchase(steamId, transactionId) {
const response = await steamWebAPI.GetUserInfo({
steamid: steamId,
transactionid: transactionId
});
return response;
}
// Purchase endpoints
app.post('/api/v1/purchase/apple', async (req, res) => {
const { transaction, userEmail } = req.body;
const validated = validateAppleTransaction(transaction);
const keys = generateKeysForPurchase(validated.productId, userEmail);
await storeUserKeys(userEmail, keys);
res.json({ keys });
});
app.post('/api/v1/purchase/google', async (req, res) => {
const { purchaseToken, productId, userEmail } = req.body;
const validated = await validateGooglePlayPurchase(purchaseToken, productId);
const keys = generateKeysForPurchase(productId, userEmail);
await storeUserKeys(userEmail, keys);
res.json({ keys });
});
app.post('/api/v1/purchase/stripe', async (req, res) => {
const { paymentIntentId, userEmail } = req.body;
const validated = await validateStripePayment(paymentIntentId);
const keys = generateKeysForPurchase(validated.metadata.productId, userEmail);
await storeUserKeys(userEmail, keys);
res.json({ keys });
});
app.post('/api/v1/purchase/paypal', async (req, res) => {
const { paymentId, userEmail } = req.body;
const validated = await validatePayPalPayment(paymentId);
const keys = generateKeysForPurchase(validated.transactions[0].item_list.items[0].sku, userEmail);
await storeUserKeys(userEmail, keys);
res.json({ keys });
});
app.post('/api/v1/purchase/steam', async (req, res) => {
const { steamId, transactionId } = req.body;
const validated = await validateSteamPurchase(steamId, transactionId);
const keys = generateKeysForPurchase(validated.productId, steamId);
await storeUserKeys(steamId, keys, 'steam');
res.json({ keys });
});
// Key sync endpoints
app.get('/api/v1/keys/email/:email', async (req, res) => {
const { email } = req.params;
const keys = await getUserKeys(email);
res.json({ keys });
});
app.get('/api/v1/keys/steam/:steamId', async (req, res) => {
const { steamId } = req.params;
const keys = await getUserKeys(steamId, 'steam');
res.json({ keys });
});
```
## Security Features
### Transaction Integrity
- Apple JWS signature validation
- HTTPS for all API calls
- Rate limiting on sync endpoints
- Input validation and sanitization
### Key Protection
- Hardware-backed keystore (iOS Keychain)
- Encrypted local storage
- Local-only key validation
- No device binding required (offline-first)
## Error Handling
### Network Errors (Sync Only)
- Graceful failure - app continues working offline
- Retry logic with exponential backoff
- User feedback on sync failures
- Cached keys always work
### Sync Errors
- Invalid email/token handling
- Server unavailable scenarios
- Network timeout handling
- Malformed response handling
## Configuration
### Server Configuration
```json
{
"keyserver": {
"baseUrl": "https://api.voiceuwu.com",
"timeout": 15000,
"retryAttempts": 2
},
"apple": {
"bundleId": "com.uwuapps.VoiceUwu",
"environment": "production"
}
}
```
### Client Configuration
```swift
struct VUKeyServerConfig {
let baseUrl: URL
let timeout: TimeInterval = 15.0 // Shorter timeout for sync
let retryAttempts: Int = 2 // Fewer retries for sync
let autoSyncOnOpen: Bool = true // Auto-sync when Keys for All opens
let syncCooldown: TimeInterval = 300 // 5 minutes between auto-syncs
}
```
## Testing Strategy
### Unit Tests
- Mock keyserver responses for sync operations
- Apple transaction validation
- Offline-first key validation logic
- Error handling scenarios (network failures)
### Integration Tests
- End-to-end purchase flow
- Keys for All sync functionality
- Network failure scenarios (app continues working)
- Cross-platform key sharing
## Implementation Notes
1. **Offline-First**: App must work completely offline - network only for sync/purchase
2. **Multi-Platform Support**: Single keyserver supports Apple, Google, Stripe, PayPal, and Steam
3. **Platform Requirements**:
- Apple: StoreKit 2 for iOS/macOS
- Google: Play Billing API for Android
- Stripe: Payment Intents API for web/direct
- PayPal: REST API for web/direct
- Steam: Web API for PC gaming
4. **Server Validation**: Each platform has its own validation method
5. **Minimal Network**: 8 endpoints total (5 purchase + 3 sync)
6. **User Experience**: Auto-sync on feature open + manual sync button
7. **Cross-Platform Keys**: Same keys work across all platforms once synced
8. **No Session Management**: Stateless key retrieval by email/token/steamId
9. **Security**: Local key validation, HTTPS for sync, encrypted storage
10. **Web Portal Integration**: Keys purchased through web portal immediately sync to all platforms
## Related Documentation
- **[VU Key Web Portal](./VUKeyWebPortal.md)**: User-facing web application for key management, sharing, and purchasing