use serde::Deserialize;
use serde_json::{Value, from_str, from_value};
use super::{
BASE64, DateTime, Engine, LicenseError, MAX_SEAT_QUANTITY, Utc, VerifiedLicenseClaims, VerifyingKey, parse_time,
recheck_claims, verify_signature,
};
#[derive(Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct LegacyEnvelope {
signature_version: String,
iv: String,
payload: String,
signature: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LegacyPayload {
issued_at: String,
expires_at: String,
entity: String,
issuer: String,
data: LegacyData,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LegacyData {
id: String,
workspace_id: String,
plan: String,
quantity: i32,
recurring: String,
end_at: String,
}
pub(super) fn verify(
envelope: Value,
key: &VerifyingKey,
workspace_id: Option<&str>,
now: DateTime<Utc>,
) -> Result<VerifiedLicenseClaims, LicenseError> {
let envelope: LegacyEnvelope = from_value(envelope).map_err(|_| LicenseError::InvalidEnvelope)?;
if envelope.signature_version != "legacy-v0" {
return Err(LicenseError::UnknownFormat);
}
let iv = BASE64.decode(&envelope.iv).map_err(|_| LicenseError::InvalidEnvelope)?;
if iv.len() != 12 {
return Err(LicenseError::InvalidEnvelope);
}
let signature = hex::decode(&envelope.signature).map_err(|_| LicenseError::InvalidSignature)?;
let mut message = iv;
message.extend_from_slice(envelope.payload.as_bytes());
verify_signature(&message, &signature, key)?;
let payload: LegacyPayload = from_str(&envelope.payload).map_err(|_| LicenseError::InvalidClaims)?;
if payload.data.plan != "selfhostedteam" {
return Err(LicenseError::UnknownPlan);
}
if payload.entity.is_empty()
|| payload.issuer.is_empty()
|| payload.data.id.is_empty()
|| payload.data.workspace_id.is_empty()
|| !(1..=MAX_SEAT_QUANTITY).contains(&payload.data.quantity)
|| !matches!(payload.data.recurring.as_str(), "monthly" | "yearly" | "lifetime")
{
return Err(LicenseError::InvalidClaims);
}
let issued_at = parse_time(&payload.issued_at)?;
let expires_at = parse_time(&payload.expires_at)?.min(parse_time(&payload.data.end_at)?);
if issued_at >= expires_at {
return Err(LicenseError::InvalidTimeWindow);
}
recheck_claims(
VerifiedLicenseClaims {
license_id: payload.data.id,
workspace_id: payload.data.workspace_id,
seat_quantity: payload.data.quantity,
issued_at,
recurring: Some(payload.data.recurring),
not_before: DateTime::<Utc>::MIN_UTC,
expires_at,
},
workspace_id,
now,
)
}