remora/crates/remora-identity/src/principal.rs
ms2k bf028161ae Add identity and target authorization package
OIDC validation, principal mapping, remora:v1 roles, and current-authz
filtering with kind acceptance on Keycloak and ZITADEL.
2026-07-16 18:27:42 +09:00

51 lines
1.5 KiB
Rust

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<StorageScope>,
}
/// 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<OpaqueId, IdentityError> {
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::<String>();
OpaqueId::parse(format!("p{hex}")).map_err(Into::into)
}