···
pub mod credential_session;
9
+
use core::future::Future;
11
+
use jacquard_common::AuthorizationToken;
12
+
use jacquard_common::error::TransportError;
pub use jacquard_common::error::{ClientError, XrpcResult};
pub use jacquard_common::session::{MemorySessionStore, SessionStore, SessionStoreError};
15
+
use jacquard_common::types::xrpc::{CallOptions, Response, XrpcClient, XrpcRequest};
types::string::{Did, Handle},
20
+
use jacquard_common::{http_client::HttpClient, types::xrpc::XrpcExt};
21
+
use jacquard_identity::resolver::IdentityResolver;
22
+
use jacquard_oauth::authstore::ClientAuthStore;
23
+
use jacquard_oauth::client::OAuthSession;
24
+
use jacquard_oauth::dpop::DpopExt;
25
+
use jacquard_oauth::resolver::OAuthResolver;
pub use token::FileAuthStore;
28
+
use crate::client::credential_session::{CredentialSession, SessionKey};
pub(crate) const NSID_REFRESH_SESSION: &str = "com.atproto.server.refreshSession";
···
67
-
#[derive(Debug, Clone)]
68
-
pub enum AuthSession {
69
-
AppPassword(AtpSession),
70
-
OAuth(jacquard_oauth::session::ClientSessionData<'static>),
80
+
/// A unified indicator for the type of authenticated session.
81
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82
+
pub enum AgentKind {
83
+
/// App password (Bearer) session
85
+
/// OAuth (DPoP) session
89
+
/// Common interface for stateful sessions used by the Agent wrapper.
90
+
pub trait AgentSession: XrpcClient + HttpClient + Send + Sync {
91
+
/// Identify the kind of session.
92
+
fn session_kind(&self) -> AgentKind;
93
+
/// Return current DID and an optional session id (always Some for OAuth).
96
+
) -> core::pin::Pin<
97
+
Box<dyn Future<Output = Option<(Did<'static>, Option<CowStr<'static>>)>> + Send + '_>,
99
+
/// Current base endpoint.
100
+
fn endpoint(&self) -> core::pin::Pin<Box<dyn Future<Output = url::Url> + Send + '_>>;
101
+
/// Override per-session call options.
102
+
fn set_options<'a>(
104
+
opts: CallOptions<'a>,
105
+
) -> core::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
106
+
/// Refresh the session and return a fresh AuthorizationToken.
109
+
) -> core::pin::Pin<
110
+
Box<dyn Future<Output = Result<AuthorizationToken<'static>, ClientError>> + Send + '_>,
74
-
pub fn did(&self) -> &Did<'static> {
76
-
AuthSession::AppPassword(session) => &session.did,
77
-
AuthSession::OAuth(session) => &session.token_set.sub,
114
+
impl<S, T> AgentSession for CredentialSession<S, T>
116
+
S: SessionStore<SessionKey, AtpSession> + Send + Sync + 'static,
117
+
T: IdentityResolver + HttpClient + XrpcExt + Send + Sync + 'static,
119
+
fn session_kind(&self) -> AgentKind {
120
+
AgentKind::AppPassword
124
+
) -> core::pin::Pin<
125
+
Box<dyn Future<Output = Option<(Did<'static>, Option<CowStr<'static>>)>> + Send + '_>,
127
+
Box::pin(async move {
128
+
CredentialSession::<S, T>::session_info(self)
130
+
.map(|(did, sid)| (did, Some(sid)))
133
+
fn endpoint(&self) -> core::pin::Pin<Box<dyn Future<Output = url::Url> + Send + '_>> {
134
+
Box::pin(async move { CredentialSession::<S, T>::endpoint(self).await })
136
+
fn set_options<'a>(
138
+
opts: CallOptions<'a>,
139
+
) -> core::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
140
+
Box::pin(async move { CredentialSession::<S, T>::set_options(self, opts).await })
144
+
) -> core::pin::Pin<
145
+
Box<dyn Future<Output = Result<AuthorizationToken<'static>, ClientError>> + Send + '_>,
147
+
Box::pin(async move {
148
+
Ok(CredentialSession::<S, T>::refresh(self)
81
-
pub fn refresh_token(&self) -> Option<&CowStr<'static>> {
83
-
AuthSession::AppPassword(session) => Some(&session.refresh_jwt),
84
-
AuthSession::OAuth(session) => session.token_set.refresh_token.as_ref(),
155
+
impl<T, S> AgentSession for OAuthSession<T, S>
157
+
S: ClientAuthStore + Send + Sync + 'static,
158
+
T: OAuthResolver + DpopExt + XrpcExt + Send + Sync + 'static,
160
+
fn session_kind(&self) -> AgentKind {
165
+
) -> core::pin::Pin<
166
+
Box<dyn Future<Output = Option<(Did<'static>, Option<CowStr<'static>>)>> + Send + '_>,
168
+
Box::pin(async move {
169
+
let (did, sid) = OAuthSession::<T, S>::session_info(self).await;
170
+
Some((did.into_static(), Some(sid.into_static())))
173
+
fn endpoint(&self) -> core::pin::Pin<Box<dyn Future<Output = url::Url> + Send + '_>> {
174
+
Box::pin(async move { self.endpoint().await })
176
+
fn set_options<'a>(
178
+
opts: CallOptions<'a>,
179
+
) -> core::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
180
+
Box::pin(async move { self.set_options(opts).await })
184
+
) -> core::pin::Pin<
185
+
Box<dyn Future<Output = Result<AuthorizationToken<'static>, ClientError>> + Send + '_>,
187
+
Box::pin(async move {
190
+
.map(|t| t.into_static())
191
+
.map_err(|e| ClientError::Transport(TransportError::Other(Box::new(e))))
88
-
pub fn access_token(&self) -> &CowStr<'static> {
90
-
AuthSession::AppPassword(session) => &session.access_jwt,
91
-
AuthSession::OAuth(session) => &session.token_set.access_token,
196
+
/// Thin wrapper that erases the concrete session type while preserving type-safety.
197
+
pub struct Agent<A: AgentSession> {
201
+
impl<A: AgentSession> Agent<A> {
202
+
/// Wrap an existing session in an Agent.
203
+
pub fn new(inner: A) -> Self {
207
+
/// Return the underlying session kind.
208
+
pub fn kind(&self) -> AgentKind {
209
+
self.inner.session_kind()
212
+
/// Return session info if available.
213
+
pub async fn info(&self) -> Option<(Did<'static>, Option<CowStr<'static>>)> {
214
+
self.inner.session_info().await
217
+
/// Get current endpoint.
218
+
pub async fn endpoint(&self) -> url::Url {
219
+
self.inner.endpoint().await
95
-
pub fn set_refresh_token(&mut self, token: CowStr<'_>) {
97
-
AuthSession::AppPassword(session) => {
98
-
session.refresh_jwt = token.into_static();
100
-
AuthSession::OAuth(session) => {
101
-
session.token_set.refresh_token = Some(token.into_static());
222
+
/// Override call options.
223
+
pub async fn set_options(&self, opts: CallOptions<'_>) {
224
+
self.inner.set_options(opts).await
106
-
pub fn set_access_token(&mut self, token: CowStr<'_>) {
108
-
AuthSession::AppPassword(session) => {
109
-
session.access_jwt = token.into_static();
111
-
AuthSession::OAuth(session) => {
112
-
session.token_set.access_token = token.into_static();
227
+
/// Refresh the session and return a fresh token.
228
+
pub async fn refresh(&self) -> Result<AuthorizationToken<'static>, ClientError> {
229
+
self.inner.refresh().await
118
-
impl From<AtpSession> for AuthSession {
119
-
fn from(session: AtpSession) -> Self {
120
-
AuthSession::AppPassword(session)
233
+
impl<A: AgentSession> HttpClient for Agent<A> {
234
+
type Error = <A as HttpClient>::Error;
238
+
request: http::Request<Vec<u8>>,
239
+
) -> impl Future<Output = core::result::Result<http::Response<Vec<u8>>, Self::Error>> + Send
241
+
self.inner.send_http(request)
124
-
impl From<jacquard_oauth::session::ClientSessionData<'static>> for AuthSession {
125
-
fn from(session: jacquard_oauth::session::ClientSessionData<'static>) -> Self {
126
-
AuthSession::OAuth(session)
245
+
impl<A: AgentSession> XrpcClient for Agent<A> {
246
+
fn base_uri(&self) -> url::Url {
247
+
self.inner.base_uri()
249
+
fn opts(&self) -> impl Future<Output = CallOptions<'_>> {
252
+
fn send<R: XrpcRequest + Send>(
255
+
) -> impl Future<Output = XrpcResult<Response<R>>> {
256
+
async move { self.inner.send(request).await }