use remora_domain::{OpaqueId, StorageScope}; use sha2::{Digest, Sha256}; use crate::error::IdentityError; use crate::roles::DecodedRoles; /// Authenticated caller after token validation and role decoding. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Principal { pub id: OpaqueId, pub issuer: String, pub subject: String, pub administrator: bool, pub scopes: Vec, } /// OIDC identity pair before durable principal-id binding. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExternalIdentity { pub issuer: String, pub subject: String, pub roles: DecodedRoles, } impl Principal { pub fn from_external(id: OpaqueId, external: ExternalIdentity) -> Self { Self { id, issuer: external.issuer, subject: external.subject, administrator: external.roles.administrator, scopes: external.roles.scopes, } } } /// Deterministic stable principal id for one exact issuer+subject pair. pub fn principal_id_for(issuer: &str, subject: &str) -> Result { let mut hasher = Sha256::new(); hasher.update(b"remora-principal-v1\0"); hasher.update(issuer.as_bytes()); hasher.update([0]); hasher.update(subject.as_bytes()); let digest = hasher.finalize(); let hex = digest .iter() .take(16) .map(|byte| format!("{byte:02x}")) .collect::(); OpaqueId::parse(format!("p{hex}")).map_err(Into::into) }