+13
crates/atproto-client/src/client.rs
+13
crates/atproto-client/src/client.rs
···
32
pub access_token: String,
33
}
34
35
+
/// Authentication method for AT Protocol XRPC requests.
36
+
///
37
+
/// Supports multiple authentication schemes including unauthenticated requests,
38
+
/// DPoP (Demonstration of Proof-of-Possession) tokens, and app password bearer tokens.
39
+
pub enum Auth {
40
+
/// No authentication - for public endpoints that don't require authentication
41
+
None,
42
+
/// DPoP authentication with proof-of-possession tokens and OAuth access token
43
+
DPoP(DPoPAuth),
44
+
/// App password authentication using JWT bearer tokens
45
+
AppPassword(AppPasswordAuth)
46
+
}
47
+
48
/// Performs an unauthenticated HTTP GET request and parses the response as JSON.
49
///
50
/// # Arguments
+13
-9
crates/atproto-client/src/com_atproto_identity.rs
+13
-9
crates/atproto-client/src/com_atproto_identity.rs
···
9
use serde::{Deserialize, de::DeserializeOwned};
10
11
use crate::{
12
-
client::{get_dpop_json, get_json, DPoPAuth}, errors::SimpleError, url::URLBuilder
13
};
14
15
/// Response from the com.atproto.identity.resolveHandle XRPC method.
···
42
/// # Arguments
43
///
44
/// * `http_client` - The HTTP client to use for the request
45
-
/// * `dpop_auth` - Optional DPoP authentication credentials
46
/// * `base_url` - The base URL of the AT Protocol service
47
/// * `handle` - The handle to resolve
48
///
···
52
/// or an error response from the server.
53
pub async fn resolve_handle<T: DeserializeOwned>(
54
http_client: &reqwest::Client,
55
-
dpop_auth: Option<&DPoPAuth>,
56
base_url: &str,
57
handle: String,
58
) -> Result<ResolveHandleResponse> {
···
63
64
let url = url_builder.build();
65
66
-
if let Some(dpop_auth) = dpop_auth {
67
-
get_dpop_json(http_client, dpop_auth, &url)
68
.await
69
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
70
-
} else {
71
-
get_json(http_client, &url)
72
.await
73
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
74
}
75
}
···
9
use serde::{Deserialize, de::DeserializeOwned};
10
11
use crate::{
12
+
client::{get_apppassword_json, get_dpop_json, get_json, Auth},
13
+
errors::SimpleError,
14
+
url::URLBuilder
15
};
16
17
/// Response from the com.atproto.identity.resolveHandle XRPC method.
···
44
/// # Arguments
45
///
46
/// * `http_client` - The HTTP client to use for the request
47
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
48
/// * `base_url` - The base URL of the AT Protocol service
49
/// * `handle` - The handle to resolve
50
///
···
54
/// or an error response from the server.
55
pub async fn resolve_handle<T: DeserializeOwned>(
56
http_client: &reqwest::Client,
57
+
auth: &Auth,
58
base_url: &str,
59
handle: String,
60
) -> Result<ResolveHandleResponse> {
···
65
66
let url = url_builder.build();
67
68
+
match auth {
69
+
Auth::None => get_json(http_client, &url)
70
.await
71
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
72
+
Auth::DPoP(dpop_auth) => get_dpop_json(http_client, dpop_auth, &url)
73
+
.await
74
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
75
+
Auth::AppPassword(app_auth) => get_apppassword_json(http_client, app_auth, &url)
76
.await
77
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
78
}
79
}
+60
-32
crates/atproto-client/src/com_atproto_repo.rs
+60
-32
crates/atproto-client/src/com_atproto_repo.rs
···
29
use serde::{Deserialize, Serialize, de::DeserializeOwned};
30
31
use crate::{
32
-
client::{DPoPAuth, get_bytes, get_dpop_json, get_json, post_dpop_json},
33
errors::SimpleError,
34
url::URLBuilder,
35
};
···
90
/// # Arguments
91
///
92
/// * `http_client` - HTTP client for making requests
93
-
/// * `dpop_auth` - DPoP authentication credentials
94
/// * `base_url` - Base URL of the AT Protocol server
95
/// * `repo` - Repository identifier (DID)
96
/// * `collection` - Collection NSID
···
102
/// The record data or an error response
103
pub async fn get_record(
104
http_client: &reqwest::Client,
105
-
dpop_auth: Option<&DPoPAuth>,
106
base_url: &str,
107
repo: &str,
108
collection: &str,
···
122
123
let url = url_builder.build();
124
125
-
if let Some(dpop_auth) = dpop_auth {
126
-
get_dpop_json(http_client, dpop_auth, &url)
127
.await
128
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
129
-
} else {
130
-
get_json(http_client, &url)
131
.await
132
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
133
}
134
}
135
···
196
/// # Arguments
197
///
198
/// * `http_client` - HTTP client for making requests
199
-
/// * `dpop_auth` - DPoP authentication credentials
200
/// * `base_url` - Base URL of the AT Protocol server
201
/// * `repo` - Repository identifier (DID)
202
/// * `collection` - Collection NSID to list from
···
207
/// A paginated list of records from the collection
208
pub async fn list_records<T: DeserializeOwned>(
209
http_client: &reqwest::Client,
210
-
dpop_auth: Option<&DPoPAuth>,
211
base_url: &str,
212
repo: String,
213
collection: String,
···
234
235
let url = url_builder.build();
236
237
-
if let Some(dpop_auth) = dpop_auth {
238
-
get_dpop_json(http_client, dpop_auth, &url)
239
.await
240
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
241
-
} else {
242
-
get_json(http_client, &url)
243
.await
244
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
245
}
246
}
247
···
299
/// # Arguments
300
///
301
/// * `http_client` - HTTP client for making requests
302
-
/// * `dpop_auth` - DPoP authentication credentials
303
/// * `base_url` - Base URL of the AT Protocol server
304
/// * `record` - Record creation request with content and metadata
305
///
···
308
/// The created record reference or an error response
309
pub async fn create_record<T: DeserializeOwned + Serialize>(
310
http_client: &reqwest::Client,
311
-
dpop_auth: &DPoPAuth,
312
base_url: &str,
313
record: CreateRecordRequest<T>,
314
) -> Result<CreateRecordResponse> {
···
318
319
let value = serde_json::to_value(record)?;
320
321
-
post_dpop_json(http_client, dpop_auth, &url, value)
322
-
.await
323
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
324
}
325
326
/// Request to update an existing record in an AT Protocol repository.
···
385
/// # Arguments
386
///
387
/// * `http_client` - HTTP client for making requests
388
-
/// * `dpop_auth` - DPoP authentication credentials
389
/// * `base_url` - Base URL of the AT Protocol server
390
/// * `record` - Record update request with new content and metadata
391
///
···
394
/// The updated record reference or an error response
395
pub async fn put_record<T: DeserializeOwned + Serialize>(
396
http_client: &reqwest::Client,
397
-
dpop_auth: &DPoPAuth,
398
base_url: &str,
399
record: PutRecordRequest<T>,
400
) -> Result<PutRecordResponse> {
···
404
405
let value = serde_json::to_value(record)?;
406
407
-
post_dpop_json(http_client, dpop_auth, &url, value)
408
-
.await
409
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
410
}
411
412
/// Request to delete a record from an AT Protocol repository.
···
460
/// # Arguments
461
///
462
/// * `http_client` - HTTP client for making requests
463
-
/// * `dpop_auth` - DPoP authentication credentials
464
/// * `base_url` - Base URL of the AT Protocol server
465
/// * `record` - Record deletion request with repository, collection, and key
466
///
···
469
/// The deletion response with commit information or an error
470
pub async fn delete_record(
471
http_client: &reqwest::Client,
472
-
dpop_auth: &DPoPAuth,
473
base_url: &str,
474
record: DeleteRecordRequest,
475
) -> Result<DeleteRecordResponse> {
···
479
480
let value = serde_json::to_value(record)?;
481
482
-
post_dpop_json(http_client, dpop_auth, &url, value)
483
-
.await
484
-
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
485
}
···
29
use serde::{Deserialize, Serialize, de::DeserializeOwned};
30
31
use crate::{
32
+
client::{Auth, get_apppassword_json, get_bytes, get_dpop_json, get_json, post_apppassword_json, post_dpop_json, post_json},
33
errors::SimpleError,
34
url::URLBuilder,
35
};
···
90
/// # Arguments
91
///
92
/// * `http_client` - HTTP client for making requests
93
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
94
/// * `base_url` - Base URL of the AT Protocol server
95
/// * `repo` - Repository identifier (DID)
96
/// * `collection` - Collection NSID
···
102
/// The record data or an error response
103
pub async fn get_record(
104
http_client: &reqwest::Client,
105
+
auth: &Auth,
106
base_url: &str,
107
repo: &str,
108
collection: &str,
···
122
123
let url = url_builder.build();
124
125
+
match auth {
126
+
Auth::None => get_json(http_client, &url)
127
+
.await
128
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
129
+
Auth::DPoP(dpop_auth) => get_dpop_json(http_client, dpop_auth, &url)
130
.await
131
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
132
+
Auth::AppPassword(app_auth) => get_apppassword_json(http_client, app_auth, &url)
133
.await
134
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
135
}
136
}
137
···
198
/// # Arguments
199
///
200
/// * `http_client` - HTTP client for making requests
201
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
202
/// * `base_url` - Base URL of the AT Protocol server
203
/// * `repo` - Repository identifier (DID)
204
/// * `collection` - Collection NSID to list from
···
209
/// A paginated list of records from the collection
210
pub async fn list_records<T: DeserializeOwned>(
211
http_client: &reqwest::Client,
212
+
auth: &Auth,
213
base_url: &str,
214
repo: String,
215
collection: String,
···
236
237
let url = url_builder.build();
238
239
+
match auth {
240
+
Auth::None => get_json(http_client, &url)
241
.await
242
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
243
+
Auth::DPoP(dpop_auth) => get_dpop_json(http_client, dpop_auth, &url)
244
.await
245
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
246
+
Auth::AppPassword(app_auth) => get_apppassword_json(http_client, app_auth, &url)
247
+
.await
248
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
249
}
250
}
251
···
303
/// # Arguments
304
///
305
/// * `http_client` - HTTP client for making requests
306
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
307
/// * `base_url` - Base URL of the AT Protocol server
308
/// * `record` - Record creation request with content and metadata
309
///
···
312
/// The created record reference or an error response
313
pub async fn create_record<T: DeserializeOwned + Serialize>(
314
http_client: &reqwest::Client,
315
+
auth: &Auth,
316
base_url: &str,
317
record: CreateRecordRequest<T>,
318
) -> Result<CreateRecordResponse> {
···
322
323
let value = serde_json::to_value(record)?;
324
325
+
match auth {
326
+
Auth::None => post_json(http_client, &url, value)
327
+
.await
328
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
329
+
Auth::DPoP(dpop_auth) => post_dpop_json(http_client, dpop_auth, &url, value)
330
+
.await
331
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
332
+
Auth::AppPassword(app_auth) => post_apppassword_json(http_client, app_auth, &url, value)
333
+
.await
334
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
335
+
}
336
}
337
338
/// Request to update an existing record in an AT Protocol repository.
···
397
/// # Arguments
398
///
399
/// * `http_client` - HTTP client for making requests
400
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
401
/// * `base_url` - Base URL of the AT Protocol server
402
/// * `record` - Record update request with new content and metadata
403
///
···
406
/// The updated record reference or an error response
407
pub async fn put_record<T: DeserializeOwned + Serialize>(
408
http_client: &reqwest::Client,
409
+
auth: &Auth,
410
base_url: &str,
411
record: PutRecordRequest<T>,
412
) -> Result<PutRecordResponse> {
···
416
417
let value = serde_json::to_value(record)?;
418
419
+
match auth {
420
+
Auth::None => post_json(http_client, &url, value)
421
+
.await
422
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
423
+
Auth::DPoP(dpop_auth) => post_dpop_json(http_client, dpop_auth, &url, value)
424
+
.await
425
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
426
+
Auth::AppPassword(app_auth) => post_apppassword_json(http_client, app_auth, &url, value)
427
+
.await
428
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
429
+
}
430
}
431
432
/// Request to delete a record from an AT Protocol repository.
···
480
/// # Arguments
481
///
482
/// * `http_client` - HTTP client for making requests
483
+
/// * `auth` - Authentication method (None, DPoP, or AppPassword)
484
/// * `base_url` - Base URL of the AT Protocol server
485
/// * `record` - Record deletion request with repository, collection, and key
486
///
···
489
/// The deletion response with commit information or an error
490
pub async fn delete_record(
491
http_client: &reqwest::Client,
492
+
auth: &Auth,
493
base_url: &str,
494
record: DeleteRecordRequest,
495
) -> Result<DeleteRecordResponse> {
···
499
500
let value = serde_json::to_value(record)?;
501
502
+
match auth {
503
+
Auth::None => post_json(http_client, &url, value)
504
+
.await
505
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
506
+
Auth::DPoP(dpop_auth) => post_dpop_json(http_client, dpop_auth, &url, value)
507
+
.await
508
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
509
+
Auth::AppPassword(app_auth) => post_apppassword_json(http_client, app_auth, &url, value)
510
+
.await
511
+
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into())),
512
+
}
513
}
+5
-49
crates/atproto-client/src/com_atproto_server.rs
+5
-49
crates/atproto-client/src/com_atproto_server.rs
···
23
use crate::{client::post_json, url::URLBuilder};
24
25
/// Request to create a new authentication session.
26
-
#[derive(Serialize, Clone)]
27
pub struct CreateSessionRequest {
28
/// Handle or other identifier supported by the server for the authenticating user
29
pub identifier: String,
···
34
pub auth_factor_token: Option<String>,
35
}
36
37
-
impl std::fmt::Debug for CreateSessionRequest {
38
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39
-
f.debug_struct("CreateSessionRequest")
40
-
.field("identifier", &self.identifier)
41
-
.field("password", &"[REDACTED]")
42
-
.field(
43
-
"auth_factor_token",
44
-
&self.auth_factor_token.as_ref().map(|_| "[REDACTED]"),
45
-
)
46
-
.finish()
47
-
}
48
-
}
49
-
50
/// App password session data returned from successful authentication.
51
#[derive(Deserialize, Clone)]
52
pub struct AppPasswordSession {
53
/// Distributed identifier for the authenticated account
···
64
pub refresh_jwt: String,
65
}
66
67
-
impl std::fmt::Debug for AppPasswordSession {
68
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69
-
f.debug_struct("AppPasswordSession")
70
-
.field("did", &self.did)
71
-
.field("handle", &self.handle)
72
-
.field("email", &self.email)
73
-
.field("access_jwt", &"[REDACTED]")
74
-
.field("refresh_jwt", &"[REDACTED]")
75
-
.finish()
76
-
}
77
-
}
78
-
79
/// Response from refreshing an authentication session.
80
#[derive(Deserialize, Clone)]
81
pub struct RefreshSessionResponse {
82
/// Distributed identifier for the authenticated account
···
97
pub status: Option<String>,
98
}
99
100
-
impl std::fmt::Debug for RefreshSessionResponse {
101
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102
-
f.debug_struct("RefreshSessionResponse")
103
-
.field("did", &self.did)
104
-
.field("handle", &self.handle)
105
-
.field("access_jwt", &"[REDACTED]")
106
-
.field("refresh_jwt", &"[REDACTED]")
107
-
.field("active", &self.active)
108
-
.field("status", &self.status)
109
-
.finish()
110
-
}
111
-
}
112
-
113
/// Response from creating a new app password.
114
#[derive(Deserialize, Clone)]
115
pub struct AppPasswordResponse {
116
/// Name of the app password
···
120
/// Creation timestamp in ISO 8601 format
121
#[serde(rename = "createdAt")]
122
pub created_at: String,
123
-
}
124
-
125
-
impl std::fmt::Debug for AppPasswordResponse {
126
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127
-
f.debug_struct("AppPasswordResponse")
128
-
.field("name", &self.name)
129
-
.field("password", &"[REDACTED]")
130
-
.field("created_at", &self.created_at)
131
-
.finish()
132
-
}
133
}
134
135
/// Creates a new authentication session using app password credentials.
···
23
use crate::{client::post_json, url::URLBuilder};
24
25
/// Request to create a new authentication session.
26
+
#[cfg_attr(debug_assertions, derive(Debug))]
27
+
#[derive(Serialize, Deserialize, Clone)]
28
pub struct CreateSessionRequest {
29
/// Handle or other identifier supported by the server for the authenticating user
30
pub identifier: String,
···
35
pub auth_factor_token: Option<String>,
36
}
37
38
/// App password session data returned from successful authentication.
39
+
#[cfg_attr(debug_assertions, derive(Debug))]
40
#[derive(Deserialize, Clone)]
41
pub struct AppPasswordSession {
42
/// Distributed identifier for the authenticated account
···
53
pub refresh_jwt: String,
54
}
55
56
/// Response from refreshing an authentication session.
57
+
#[cfg_attr(debug_assertions, derive(Debug))]
58
#[derive(Deserialize, Clone)]
59
pub struct RefreshSessionResponse {
60
/// Distributed identifier for the authenticated account
···
75
pub status: Option<String>,
76
}
77
78
/// Response from creating a new app password.
79
+
#[cfg_attr(debug_assertions, derive(Debug))]
80
#[derive(Deserialize, Clone)]
81
pub struct AppPasswordResponse {
82
/// Name of the app password
···
86
/// Creation timestamp in ISO 8601 format
87
#[serde(rename = "createdAt")]
88
pub created_at: String,
89
}
90
91
/// Creates a new authentication session using app password credentials.
+3
crates/atproto-oauth-aip/src/workflow.rs
+3
crates/atproto-oauth-aip/src/workflow.rs
···
161
pub handle: String,
162
163
/// The OAuth access token for making authenticated requests.
164
pub access_token: String,
165
166
/// The type of token (typically "Bearer").
167
pub token_type: String,
168
169
/// The list of OAuth scopes granted to this session.
···
175
pub pds_endpoint: String,
176
177
/// The DPoP (Demonstration of Proof-of-Possession) key in JWK format.
178
pub dpop_key: String,
179
180
/// Unix timestamp indicating when this session expires.
···
161
pub handle: String,
162
163
/// The OAuth access token for making authenticated requests.
164
+
#[cfg_attr(feature = "zeroize", zeroize(skip))]
165
pub access_token: String,
166
167
/// The type of token (typically "Bearer").
168
+
#[cfg_attr(feature = "zeroize", zeroize(skip))]
169
pub token_type: String,
170
171
/// The list of OAuth scopes granted to this session.
···
177
pub pds_endpoint: String,
178
179
/// The DPoP (Demonstration of Proof-of-Possession) key in JWK format.
180
+
#[cfg_attr(feature = "zeroize", zeroize(skip))]
181
pub dpop_key: String,
182
183
/// Unix timestamp indicating when this session expires.
+1
-2
crates/atproto-record/src/bin/atproto-record-sign.rs
+1
-2
crates/atproto-record/src/bin/atproto-record-sign.rs
+1
-1
crates/atproto-record/src/bin/atproto-record-verify.rs
+1
-1
crates/atproto-record/src/bin/atproto-record-verify.rs