affine_core 0.0.5

AFFiNE primitive core.
Documentation
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),
      // Old licenses had no not-before restriction; do not invent one during import.
      not_before: DateTime::<Utc>::MIN_UTC,
      expires_at,
    },
    workspace_id,
    now,
  )
}