1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
use ;
use ;
use Uuid;
/// Request to activate a license on a specific device.
///
/// This struct is sent from the client application to the license server
/// to request activation of a license. It includes hardware fingerprinting
/// and device metadata for license binding and tracking purposes.
///
/// # Fields
/// - `license_id`: Unique identifier of the license to activate
/// - `hardware_fingerprint`: SHA-256 hash of hardware identifiers for device binding
/// - `machine_name`: Human-readable name of the device/machine
/// - `ip_address`: Optional client IP address for geolocation and security
/// - `user_agent`: Optional client software/version for compatibility tracking
/// - `timestamp`: When the activation request was created (UTC)
/// - `nonce`: Unique one-time value to prevent replay attacks
///
/// # Security Considerations
/// - The `hardware_fingerprint` should be generated client-side to avoid transmitting raw hardware data
/// - `nonce` prevents replay attacks by ensuring each request is unique
/// - Timestamp allows detection of delayed or manipulated requests
///
/// # Example JSON
/// ```json
/// {
/// "license_id": "123e4567-e89b-12d3-a456-426614174000",
/// "hardware_fingerprint": "a1b2c3d4e5f67890...",
/// "machine_name": "Johns-MacBook-Pro",
/// "ip_address": "192.168.1.100",
/// "user_agent": "MyApp/2.0.0 (macOS 14.0)",
/// "timestamp": "2024-01-01T12:00:00Z",
/// "nonce": "550e8400-e29b-41d4-a716-446655440000"
/// }
/// ```
/// Server response to a successful license activation.
///
/// Returned by the license server when activation is approved.
/// Contains tokens for future validation and metadata about the activation.
///
/// # Fields
/// - `activation_id`: Unique identifier for this specific activation instance
/// - `license_id`: The license that was activated (matches request)
/// - `activated_at`: Server timestamp when activation was recorded
/// - `expires_at`: When this activation expires (may differ from license expiration)
/// - `activation_token`: JWT or similar token for subsequent validations
/// - `refresh_token`: Optional token for refreshing the activation token without re-activation
/// - `hardware_bound`: Whether this activation is tied to specific hardware
/// - `max_activations`: Maximum concurrent activations allowed for this license
/// - `current_activations`: Current number of active activations for this license
///
/// # Token Security
/// - `activation_token`: Short-lived token for frequent validations
/// - `refresh_token`: Long-lived token stored securely, used to obtain new activation tokens
/// - Both tokens should be transmitted and stored securely (HTTPS, secure storage)
///
/// # Example JSON
/// ```json
/// {
/// "activation_id": "223e4567-e89b-12d3-a456-426614174001",
/// "license_id": "123e4567-e89b-12d3-a456-426614174000",
/// "activated_at": "2024-01-01T12:00:05Z",
/// "expires_at": "2024-12-31T23:59:59Z",
/// "activation_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
/// "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
/// "hardware_bound": true,
/// "max_activations": 5,
/// "current_activations": 1
/// }
/// ```
/// Database record tracking a license activation.
///
/// This struct represents the server-side persistent record of an activation.
/// Used for auditing, management, and revocation purposes.
///
/// # Fields
/// - `id`: Unique identifier of this activation record
/// - `license_id`: Reference to the activated license
/// - `hardware_fingerprint`: Hashed hardware identifiers for this activation
/// - `machine_name`: Name of the machine where activated
/// - `activated_at`: When the activation was created
/// - `last_checkin`: Last time this activation contacted the server (for heartbeat)
/// - `is_active`: Whether this activation is currently considered active
/// - `ip_address`: IP address from which activation was requested
///
/// # Database Considerations
/// - Used in SQL/NoSQL databases for activation tracking
/// - `last_checkin` allows cleanup of stale activations
/// - `is_active` enables soft deactivation without deleting history
/// - Indexes should be created on `license_id` and `hardware_fingerprint`
///
/// # Example Database Record
/// ```sql
/// INSERT INTO activations (
/// id, license_id, hardware_fingerprint, machine_name,
/// activated_at, last_checkin, is_active, ip_address
/// ) VALUES (
/// '223e4567-e89b-12d3-a456-426614174001',
/// '123e4567-e89b-12d3-a456-426614174000',
/// 'a1b2c3d4e5f67890...',
/// 'Johns-MacBook-Pro',
/// '2024-01-01T12:00:05Z',
/// '2024-01-02T12:00:00Z',
/// true,
/// '192.168.1.100'
/// );
/// ```
// Note: UUIDs are used throughout for identifiers because:
// - Globally unique across distributed systems
// - No sequential pattern (harder to guess)
// - Standardized format (RFC 4122)
// - Good database performance with proper indexing
// Note: All timestamps use UTC to avoid timezone confusion in distributed systems:
// - Consistent across servers in different regions
// - Easier for chronological sorting and comparison
// - Standard format for JSON serialization (RFC 3339)
// Note: The `Option<T>` type is used for optional fields to clearly distinguish
// between "not provided" and "empty value". This is important for:
// - Backward compatibility when adding new fields
// - Clear API semantics
// - Database schema evolution
// Note: Activation flow typically works as:
// 1. Client generates hardware fingerprint
// 2. Client sends `ActivationRequest` to server
// 3. Server validates license, checks limits, creates `ActivationRecord`
// 4. Server returns `ActivationResponse` with tokens
// 5. Client stores tokens securely for future validations
// 6. Periodic heartbeats update `last_checkin` timestamp
// Note: Security best practices for activations:
// 1. Validate hardware fingerprint format and length
// 2. Implement rate limiting on activation attempts
// 3. Log all activation attempts for audit trail
// 4. Use secure random UUID generation for IDs and nonces
// 5. Implement token expiration and rotation policies
// 6. Encrypt sensitive data at rest in database
// Note: The `nonce` field in `ActivationRequest` serves multiple purposes:
// - Prevents replay attacks (each request must be unique)
// - Can be used for idempotency (prevent duplicate processing)
// - Helps with request ordering and deduplication
// - Should be a cryptographically secure random value
// Example workflow using these structs:
// ```
// // Client side
// let request = ActivationRequest {
// license_id: license.license_id(),
// hardware_fingerprint: HardwareFingerprint::generate()?,
// machine_name: hostname::get()?.to_string_lossy().to_string(),
// ip_address: get_public_ip().await.ok(),
// user_agent: Some(format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))),
// timestamp: Utc::now(),
// nonce: Uuid::new_v4().to_string(),
// };
//
// // Send to server
// let response: ActivationResponse = client.post("/activate")
// .json(&request)
// .send()
// .await?
// .json()
// .await?;
//
// // Store tokens securely
// save_token("activation_token", &response.activation_token);
// if let Some(refresh) = &response.refresh_token {
// save_token("refresh_token", refresh);
// }
//
// // Server side (simplified)
// fn handle_activation(request: ActivationRequest) -> Result<ActivationResponse, Error> {
// // Validate license exists and is valid
// let license = licenses.get(&request.license_id)?;
//
// // Check activation limits
// let current = count_activations(&request.license_id)?;
// if current >= license.max_activations {
// return Err(Error::ActivationLimitExceeded);
// }
//
// // Create activation record
// let record = ActivationRecord {
// id: Uuid::new_v4(),
// license_id: request.license_id,
// hardware_fingerprint: request.hardware_fingerprint.clone(),
// machine_name: request.machine_name.clone(),
// activated_at: Utc::now(),
// last_checkin: Utc::now(),
// is_active: true,
// ip_address: request.ip_address.clone(),
// };
//
// // Save to database
// save_activation_record(&record)?;
//
// // Generate tokens
// let activation_token = generate_jwt(&record.id, Duration::hours(24))?;
// let refresh_token = generate_jwt(&record.id, Duration::days(30))?;
//
// // Return response
// Ok(ActivationResponse {
// activation_id: record.id,
// license_id: record.license_id,
// activated_at: record.activated_at,
// expires_at: license.expires_at,
// activation_token,
// refresh_token: Some(refresh_token),
// hardware_bound: true,
// max_activations: license.max_activations,
// current_activations: current + 1,
// })
// }
// ```