mod cookie;
mod registry;
pub mod passkey_login;
pub mod wallet_login;
pub use cookie::{
GRANT_COOKIE_NAME, SESSION_COOKIE_NAME, clear_grant_cookie_header, clear_session_cookie_header,
extract_session_cookie, set_grant_cookie_header, set_session_cookie_header,
};
pub use registry::{LoginChallenge, LoginChallengeRegistry, WebAuthnLoginConfig};
use polyc_session_family::ScopeRoot;
pub const SESSION_TTL_MS: u64 = 15 * 60 * 1000;
#[derive(Debug, Clone)]
pub struct MintedSession {
pub session_token: String,
pub grant: String,
pub family_expires_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ScopeResolutionError {
#[error("a persona-store read failed while resolving session scopes")]
Storage,
}
#[allow(async_fn_in_trait)]
pub trait PersonaSessionReader: Sync {
async fn session_profile(
&self,
persona_id: String,
) -> Result<Option<polyc_proto::proto::polychrome::persona::v1::PersonaProfile>, ()>;
async fn session_wallet_persona(&self, wallet_address: &str) -> Result<Option<String>, ()>;
async fn session_credential_persona(&self, credential_id: &[u8]) -> Result<Option<String>, ()>;
async fn session_credential(
&self,
persona_id: &str,
) -> Result<Option<polyc_persona::PersonaCredential>, ()>;
}
impl PersonaSessionReader for polyc_persona::PersonaHost {
async fn session_profile(
&self,
persona_id: String,
) -> Result<Option<polyc_proto::proto::polychrome::persona::v1::PersonaProfile>, ()> {
self.profile(persona_id).await.map_err(|_| ())
}
async fn session_wallet_persona(&self, wallet_address: &str) -> Result<Option<String>, ()> {
self.resolve_wallet_link_address(wallet_address)
.await
.map_err(|_| ())
}
async fn session_credential_persona(&self, credential_id: &[u8]) -> Result<Option<String>, ()> {
self.resolve_persona_credential(credential_id)
.await
.map_err(|_| ())
}
async fn session_credential(
&self,
persona_id: &str,
) -> Result<Option<polyc_persona::PersonaCredential>, ()> {
self.get_persona_credential(persona_id)
.await
.map_err(|_| ())
}
}
impl<T> PersonaSessionReader for std::sync::Arc<T>
where
T: PersonaSessionReader + Send + ?Sized,
{
async fn session_profile(
&self,
persona_id: String,
) -> Result<Option<polyc_proto::proto::polychrome::persona::v1::PersonaProfile>, ()> {
self.as_ref().session_profile(persona_id).await
}
async fn session_wallet_persona(&self, wallet_address: &str) -> Result<Option<String>, ()> {
self.as_ref().session_wallet_persona(wallet_address).await
}
async fn session_credential_persona(&self, credential_id: &[u8]) -> Result<Option<String>, ()> {
self.as_ref()
.session_credential_persona(credential_id)
.await
}
async fn session_credential(
&self,
persona_id: &str,
) -> Result<Option<polyc_persona::PersonaCredential>, ()> {
self.as_ref().session_credential(persona_id).await
}
}
pub async fn resolve_subject_and_scopes(
persona: &(impl PersonaSessionReader + ?Sized),
root: ScopeRoot,
) -> Result<
(
polyc_crypto::session::SessionSubject,
Vec<polyc_crypto::session::SessionScope>,
),
ScopeResolutionError,
> {
use polyc_crypto::session::{SessionScope, SessionSubject};
match root {
ScopeRoot::PersonaCredential { persona_id } => {
let linked = persona
.session_profile(persona_id.clone())
.await
.map_err(|()| ScopeResolutionError::Storage)?
.is_some_and(|profile| profile.status == polyc_persona::STATUS_LINKED);
let scopes = if linked {
vec![SessionScope::ExplorerRead, SessionScope::AgentTurn]
} else {
Vec::new()
};
Ok((SessionSubject::Persona { persona_id }, scopes))
}
ScopeRoot::Wallet { wallet_address } => {
let resolved_persona_id = persona
.session_wallet_persona(&wallet_address)
.await
.map_err(|()| ScopeResolutionError::Storage)?;
let mut scopes = vec![SessionScope::WalletManage];
let mut linked_persona_id = None;
if let Some(candidate) = resolved_persona_id {
let linked = persona
.session_profile(candidate.clone())
.await
.map_err(|()| ScopeResolutionError::Storage)?
.is_some_and(|profile| profile.status == polyc_persona::STATUS_LINKED);
if linked {
scopes.push(SessionScope::ExplorerRead);
scopes.push(SessionScope::AgentTurn);
linked_persona_id = Some(candidate);
}
}
Ok((
SessionSubject::Wallet {
wallet_address,
persona_id: linked_persona_id,
},
scopes,
))
}
}
}