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