+2
Cargo.lock
+2
Cargo.lock
+1
-1
crates/atproto-client/src/com_atproto_identity.rs
+1
-1
crates/atproto-client/src/com_atproto_identity.rs
···
6
6
use std::collections::HashMap;
7
7
8
8
use anyhow::Result;
9
+
use atproto_identity::url::URLBuilder;
9
10
use serde::{Deserialize, de::DeserializeOwned};
10
11
11
12
use crate::{
12
13
client::{Auth, get_apppassword_json, get_dpop_json, get_json},
13
14
errors::SimpleError,
14
-
url::URLBuilder,
15
15
};
16
16
17
17
/// Response from the com.atproto.identity.resolveHandle XRPC method.
+1
-1
crates/atproto-client/src/com_atproto_repo.rs
+1
-1
crates/atproto-client/src/com_atproto_repo.rs
···
25
25
use std::collections::HashMap;
26
26
27
27
use anyhow::Result;
28
+
use atproto_identity::url::URLBuilder;
28
29
use bytes::Bytes;
29
30
use serde::{Deserialize, Serialize, de::DeserializeOwned};
30
31
···
34
35
post_dpop_json, post_json,
35
36
},
36
37
errors::SimpleError,
37
-
url::URLBuilder,
38
38
};
39
39
40
40
/// Response from getting a record from an AT Protocol repository.
+2
-1
crates/atproto-client/src/com_atproto_server.rs
+2
-1
crates/atproto-client/src/com_atproto_server.rs
···
18
18
//! an access JWT token from an authenticated session.
19
19
20
20
use anyhow::Result;
21
+
use atproto_identity::url::URLBuilder;
21
22
use serde::{Deserialize, Serialize};
22
23
23
-
use crate::{client::post_json, url::URLBuilder};
24
+
use crate::client::post_json;
24
25
25
26
/// Request to create a new authentication session.
26
27
#[cfg_attr(debug_assertions, derive(Debug))]
-1
crates/atproto-client/src/lib.rs
-1
crates/atproto-client/src/lib.rs
crates/atproto-client/src/url.rs
crates/atproto-identity/src/url.rs
crates/atproto-client/src/url.rs
crates/atproto-identity/src/url.rs
+6
-5
crates/atproto-identity/Cargo.toml
+6
-5
crates/atproto-identity/Cargo.toml
···
44
44
45
45
[dependencies]
46
46
anyhow.workspace = true
47
+
async-trait.workspace = true
48
+
clap = { workspace = true, optional = true }
47
49
ecdsa.workspace = true
50
+
elliptic-curve.workspace = true
48
51
hickory-resolver = { workspace = true, optional = true }
49
52
k256 = { workspace = true, features = ["jwk"] }
53
+
lru = { workspace = true, optional = true }
50
54
multibase.workspace = true
51
55
p256 = { workspace = true, features = ["jwk"] }
52
56
p384 = { workspace = true, features = ["jwk"] }
57
+
rand.workspace = true
53
58
reqwest.workspace = true
54
59
serde_ipld_dagcbor.workspace = true
55
60
serde_json.workspace = true
···
57
62
thiserror.workspace = true
58
63
tokio.workspace = true
59
64
tracing.workspace = true
60
-
elliptic-curve.workspace = true
61
-
rand.workspace = true
62
-
async-trait = "0.1.88"
63
-
lru = { workspace = true, optional = true }
64
-
clap = { workspace = true, optional = true }
65
+
urlencoding.workspace = true
65
66
zeroize = { workspace = true, optional = true }
66
67
67
68
[features]
+1
crates/atproto-identity/src/lib.rs
+1
crates/atproto-identity/src/lib.rs
+1
crates/atproto-oauth-aip/Cargo.toml
+1
crates/atproto-oauth-aip/Cargo.toml
+2
-1
crates/atproto-oauth-aip/src/lib.rs
+2
-1
crates/atproto-oauth-aip/src/lib.rs
+118
-6
crates/atproto-oauth-aip/src/workflow.rs
+118
-6
crates/atproto-oauth-aip/src/workflow.rs
···
99
99
//! let session = session_exchange(
100
100
//! &http_client,
101
101
//! &protected_resource.resource,
102
-
//! &token_response.access_token
102
+
//! &token_response.access_token,
103
+
//! &None
103
104
//! ).await?;
104
105
//! # Ok(())
105
106
//! # }
···
112
113
//! and protocol violations.
113
114
114
115
use anyhow::Result;
116
+
use atproto_identity::url::URLBuilder;
115
117
use atproto_oauth::{
116
118
jwk::WrappedJsonWebKey,
117
119
workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse},
···
387
389
/// DID, handle, and PDS endpoint. This is specific to AT Protocol's OAuth
388
390
/// implementation.
389
391
///
392
+
/// This is a convenience function that calls `session_exchange_with_options`
393
+
/// with no additional options.
394
+
///
390
395
/// # Arguments
391
396
///
392
397
/// * `http_client` - The HTTP client to use for making requests
393
-
/// * `protected_resource` - The protected resource metadata
398
+
/// * `protected_resource_base` - The base URL of the protected resource (PDS)
394
399
/// * `access_token` - The OAuth access token to exchange
395
400
///
396
401
/// # Returns
···
421
426
protected_resource_base: &str,
422
427
access_token: &str,
423
428
) -> Result<ATProtocolSession> {
429
+
session_exchange_with_options(
430
+
http_client,
431
+
protected_resource_base,
432
+
access_token,
433
+
&None,
434
+
&None,
435
+
)
436
+
.await
437
+
}
438
+
439
+
/// Exchanges an OAuth access token for an AT Protocol session with additional options.
440
+
///
441
+
/// This function takes an OAuth access token and exchanges it for a full
442
+
/// AT Protocol session, which includes additional information like the user's
443
+
/// DID, handle, and PDS endpoint. This version allows specifying additional
444
+
/// options for the session exchange.
445
+
///
446
+
/// # Arguments
447
+
///
448
+
/// * `http_client` - The HTTP client to use for making requests
449
+
/// * `protected_resource_base` - The base URL of the protected resource (PDS)
450
+
/// * `access_token` - The OAuth access token to exchange
451
+
/// * `access_token_type` - Optional token type ("oauth_session", "app_password_session", or "best")
452
+
/// * `subject` - Optional subject (DID) to specify which user's session to retrieve
453
+
///
454
+
/// # Returns
455
+
///
456
+
/// Returns an `ATProtocolSession` with full session information,
457
+
/// or an error if the session exchange fails.
458
+
///
459
+
/// # Example
460
+
///
461
+
/// ```no_run
462
+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
463
+
/// use atproto_oauth_aip::workflow::session_exchange_with_options;
464
+
/// # let http_client = reqwest::Client::new();
465
+
/// # let protected_resource = "https://pds.example.com";
466
+
/// # let access_token = "example_token";
467
+
/// // Basic usage without options
468
+
/// let session = session_exchange_with_options(
469
+
/// &http_client,
470
+
/// protected_resource,
471
+
/// access_token,
472
+
/// &None,
473
+
/// &None,
474
+
/// ).await?;
475
+
/// # Ok(())
476
+
/// # }
477
+
/// ```
478
+
///
479
+
/// # Example with access_token_type
480
+
///
481
+
/// ```no_run
482
+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
483
+
/// use atproto_oauth_aip::workflow::session_exchange_with_options;
484
+
/// # let http_client = reqwest::Client::new();
485
+
/// # let protected_resource = "https://pds.example.com";
486
+
/// # let access_token = "example_token";
487
+
/// // Specify the token type
488
+
/// let session = session_exchange_with_options(
489
+
/// &http_client,
490
+
/// protected_resource,
491
+
/// access_token,
492
+
/// &Some("oauth_session"),
493
+
/// &None,
494
+
/// ).await?;
495
+
/// # Ok(())
496
+
/// # }
497
+
/// ```
498
+
///
499
+
/// # Example with subject
500
+
///
501
+
/// ```no_run
502
+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
503
+
/// use atproto_oauth_aip::workflow::session_exchange_with_options;
504
+
/// # let http_client = reqwest::Client::new();
505
+
/// # let protected_resource = "https://pds.example.com";
506
+
/// # let access_token = "example_token";
507
+
/// # let user_did = "did:plc:example123";
508
+
/// // Specify both token type and subject
509
+
/// let session = session_exchange_with_options(
510
+
/// &http_client,
511
+
/// protected_resource,
512
+
/// access_token,
513
+
/// &Some("app_password_session"),
514
+
/// &Some(user_did),
515
+
/// ).await?;
516
+
/// # Ok(())
517
+
/// # }
518
+
/// ```
519
+
pub async fn session_exchange_with_options(
520
+
http_client: &reqwest::Client,
521
+
protected_resource_base: &str,
522
+
access_token: &str,
523
+
access_token_type: &Option<&str>,
524
+
subject: &Option<&str>,
525
+
) -> Result<ATProtocolSession> {
526
+
let mut url_builder = URLBuilder::new(protected_resource_base);
527
+
url_builder.path("/api/atprotocol/session");
528
+
529
+
if let Some(value) = access_token_type {
530
+
url_builder.param("access_token_type", value);
531
+
}
532
+
533
+
if let Some(value) = subject {
534
+
url_builder.param("sub", value);
535
+
}
536
+
537
+
let url = url_builder.build();
538
+
424
539
let response = http_client
425
-
.get(format!(
426
-
"{}/api/atprotocol/session",
427
-
protected_resource_base
428
-
))
540
+
.get(url)
429
541
.bearer_auth(access_token)
430
542
.send()
431
543
.await