Download cloud-forge/crates/cloud-credentials/src/lib.rs from Snapkitty/tlm-jxcl-forge: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/Snapkitty/tlm-jxcl-forge/resolve/main/cloud-forge/crates/cloud-credentials/src/lib.rs
- Command line
-
hf download hf://Snapkitty/tlm-jxcl-forge/cloud-forge/crates/cloud-credentials/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/Snapkitty/tlm-jxcl-forge/resolve/main/cloud-forge/crates/cloud-credentials/src/lib.rs
11.7 kB
| // Copyright (C) 2026 ERRANT DIGITAL INSTITUTE OF TECHNOLOGY (SEIT NGO) % SNAPKITTY COLLECTIVE | |
| // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial | |
| // See LICENSE-AGPL and LICENSE-COMMERCIAL in this repository for license terms. | |
| //! Long-lived credential material bound to a [`Principal`]: the first | |
| //! piece of Phase 13's IAM surface, which `cloud-identity`'s own Phase | |
| //! 1 doc comment named as deferred ("credentials, sessions, | |
| //! federation, policy documents ... is Phase 13 in the roadmap"). | |
| //! | |
| //! This crate models the real IAM rule AWS itself enforces: a | |
| //! principal may hold **at most two `Active` credentials at once** | |
| //! (`Inactive` ones don't count against the cap, so a principal can | |
| //! rotate by creating a new key, deactivating the old one, and | |
| //! deleting it later without ever exceeding the limit). There is no | |
| //! real key material here -- no secret bytes, no signing -- since | |
| //! generating cryptographically-secure secrets needs a source of | |
| //! randomness this zero-external-dependency workspace deliberately | |
| //! doesn't pull in; this crate models the *lifecycle* of a credential | |
| //! identity, exactly as `cloud-lifecycle` models a resource's lifecycle | |
| //! without knowing what the resource does. | |
| //! | |
| //! Credential ids reuse `cloud_types::ResourceId` rather than a new | |
| //! wrapper type -- the same opaque, validated string identifier this | |
| //! workspace already uses for instance ids, volume ids, message ids, | |
| //! and snapshot ids, and a credential id needs nothing more than that. | |
| use cloud_errors::CloudError; | |
| use cloud_identity::Principal; | |
| use cloud_types::{ResourceId, Timestamp}; | |
| use std::collections::BTreeMap; | |
| /// The real-world IAM limit: at most this many `Active` credentials | |
| /// per principal at once. | |
| pub const MAX_ACTIVE_CREDENTIALS_PER_PRINCIPAL: usize = 2; | |
| pub enum CredentialStatus { | |
| Active, | |
| Inactive, | |
| } | |
| /// One credential's identity and lifecycle state -- no secret material. | |
| pub struct Credential { | |
| id: ResourceId, | |
| principal: Principal, | |
| status: CredentialStatus, | |
| created_at: Timestamp, | |
| } | |
| impl Credential { | |
| pub fn id(&self) -> &ResourceId { | |
| &self.id | |
| } | |
| pub fn principal(&self) -> &Principal { | |
| &self.principal | |
| } | |
| pub fn status(&self) -> CredentialStatus { | |
| self.status | |
| } | |
| pub fn created_at(&self) -> Timestamp { | |
| self.created_at | |
| } | |
| } | |
| /// A registry of credentials, indexed both by id and by owning | |
| /// principal. | |
| pub struct CredentialStore { | |
| credentials: BTreeMap<ResourceId, Credential>, | |
| by_principal: BTreeMap<Principal, Vec<ResourceId>>, | |
| } | |
| impl CredentialStore { | |
| pub fn new() -> Self { | |
| CredentialStore { | |
| credentials: BTreeMap::new(), | |
| by_principal: BTreeMap::new(), | |
| } | |
| } | |
| pub fn get(&self, id: &ResourceId) -> Option<&Credential> { | |
| self.credentials.get(id) | |
| } | |
| pub fn for_principal(&self, principal: &Principal) -> impl Iterator<Item = &Credential> { | |
| self.by_principal | |
| .get(principal) | |
| .into_iter() | |
| .flatten() | |
| .filter_map(|id| self.credentials.get(id)) | |
| } | |
| pub fn active_count(&self, principal: &Principal) -> usize { | |
| self.for_principal(principal) | |
| .filter(|c| c.status == CredentialStatus::Active) | |
| .count() | |
| } | |
| fn check_active_cap(&self, principal: &Principal) -> Result<(), CloudError> { | |
| let current = self.active_count(principal); | |
| if current >= MAX_ACTIVE_CREDENTIALS_PER_PRINCIPAL { | |
| return Err(CloudError::QuotaExceeded { | |
| resource: "active credentials".to_string(), | |
| limit: MAX_ACTIVE_CREDENTIALS_PER_PRINCIPAL as u64, | |
| requested: (current + 1) as u64, | |
| }); | |
| } | |
| Ok(()) | |
| } | |
| /// Creates a new, `Active` credential for `principal`. Rejects a | |
| /// duplicate `id`, and rejects a principal that already holds | |
| /// [`MAX_ACTIVE_CREDENTIALS_PER_PRINCIPAL`] active credentials -- | |
| /// creating an inactive one is not a way around the cap, since a | |
| /// freshly created credential always starts `Active`. | |
| pub fn create( | |
| &mut self, | |
| id: ResourceId, | |
| principal: Principal, | |
| created_at: Timestamp, | |
| ) -> Result<&Credential, CloudError> { | |
| if self.credentials.contains_key(&id) { | |
| return Err(CloudError::Conflict { | |
| what: "credential", | |
| id: id.to_string(), | |
| }); | |
| } | |
| self.check_active_cap(&principal)?; | |
| let credential = Credential { | |
| id: id.clone(), | |
| principal: principal.clone(), | |
| status: CredentialStatus::Active, | |
| created_at, | |
| }; | |
| self.credentials.insert(id.clone(), credential); | |
| self.by_principal | |
| .entry(principal) | |
| .or_default() | |
| .push(id.clone()); | |
| Ok(self | |
| .credentials | |
| .get(&id) | |
| .expect("just inserted under this exact id")) | |
| } | |
| /// Reactivates an `Inactive` credential. Rejected if doing so would | |
| /// push its principal over the active cap -- a principal cannot | |
| /// reactivate a third key while two others are already active. | |
| pub fn activate(&mut self, id: &ResourceId) -> Result<(), CloudError> { | |
| let principal = self | |
| .credentials | |
| .get(id) | |
| .ok_or_else(|| CloudError::NotFound { | |
| what: "credential", | |
| id: id.to_string(), | |
| })? | |
| .principal | |
| .clone(); | |
| if self.credentials[id].status == CredentialStatus::Active { | |
| return Ok(()); | |
| } | |
| self.check_active_cap(&principal)?; | |
| self.credentials.get_mut(id).unwrap().status = CredentialStatus::Active; | |
| Ok(()) | |
| } | |
| /// Deactivates a credential. Always succeeds for a known id -- | |
| /// deactivating can never violate the active cap. | |
| pub fn deactivate(&mut self, id: &ResourceId) -> Result<(), CloudError> { | |
| let credential = self | |
| .credentials | |
| .get_mut(id) | |
| .ok_or_else(|| CloudError::NotFound { | |
| what: "credential", | |
| id: id.to_string(), | |
| })?; | |
| credential.status = CredentialStatus::Inactive; | |
| Ok(()) | |
| } | |
| /// Permanently removes a credential. | |
| pub fn delete(&mut self, id: &ResourceId) -> Result<(), CloudError> { | |
| let credential = self | |
| .credentials | |
| .remove(id) | |
| .ok_or_else(|| CloudError::NotFound { | |
| what: "credential", | |
| id: id.to_string(), | |
| })?; | |
| if let Some(list) = self.by_principal.get_mut(&credential.principal) { | |
| list.retain(|existing| existing != id); | |
| } | |
| Ok(()) | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| fn principal(name: &str) -> Principal { | |
| Principal::User(ResourceId::new(name).unwrap()) | |
| } | |
| fn id(s: &str) -> ResourceId { | |
| ResourceId::new(s).unwrap() | |
| } | |
| fn creating_a_credential_starts_it_active() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| assert_eq!( | |
| store.get(&id("key-1")).unwrap().status(), | |
| CredentialStatus::Active | |
| ); | |
| } | |
| fn creating_a_duplicate_id_is_a_conflict() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| let err = store | |
| .create(id("key-1"), principal("bob"), Timestamp::from_millis(0)) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::Conflict { .. })); | |
| } | |
| fn a_third_active_credential_is_rejected() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store | |
| .create(id("key-2"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| let err = store | |
| .create(id("key-3"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::QuotaExceeded { .. })); | |
| assert_eq!(store.active_count(&principal("alice")), 2); | |
| } | |
| fn different_principals_have_independent_caps() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store | |
| .create(id("key-2"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store | |
| .create(id("key-3"), principal("bob"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| assert_eq!(store.active_count(&principal("bob")), 1); | |
| } | |
| fn deactivating_frees_a_slot_under_the_cap() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store | |
| .create(id("key-2"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store.deactivate(&id("key-1")).unwrap(); | |
| store | |
| .create(id("key-3"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| assert_eq!(store.active_count(&principal("alice")), 2); | |
| } | |
| fn reactivating_a_credential_can_be_rejected_by_the_cap() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store | |
| .create(id("key-2"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store.deactivate(&id("key-1")).unwrap(); | |
| store | |
| .create(id("key-3"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| // alice now has key-2 and key-3 active, key-1 inactive. | |
| let err = store.activate(&id("key-1")).unwrap_err(); | |
| assert!(matches!(err, CloudError::QuotaExceeded { .. })); | |
| } | |
| fn reactivating_an_already_active_credential_is_a_no_op() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store.activate(&id("key-1")).unwrap(); | |
| assert_eq!(store.active_count(&principal("alice")), 1); | |
| } | |
| fn deleting_a_credential_removes_it_from_its_principal() { | |
| let mut store = CredentialStore::new(); | |
| store | |
| .create(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| store.delete(&id("key-1")).unwrap(); | |
| assert!(store.get(&id("key-1")).is_none()); | |
| assert_eq!(store.for_principal(&principal("alice")).count(), 0); | |
| } | |
| fn operations_on_an_unknown_credential_are_not_found() { | |
| let mut store = CredentialStore::new(); | |
| assert!(matches!( | |
| store.activate(&id("ghost")).unwrap_err(), | |
| CloudError::NotFound { .. } | |
| )); | |
| assert!(matches!( | |
| store.deactivate(&id("ghost")).unwrap_err(), | |
| CloudError::NotFound { .. } | |
| )); | |
| assert!(matches!( | |
| store.delete(&id("ghost")).unwrap_err(), | |
| CloudError::NotFound { .. } | |
| )); | |
| } | |
| } | |