-13
Cargo.lock
-13
Cargo.lock
···
844
]
845
846
[[package]]
847
-
name = "enum_dispatch"
848
-
version = "0.3.13"
849
-
source = "registry+https://github.com/rust-lang/crates.io-index"
850
-
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
851
-
dependencies = [
852
-
"once_cell",
853
-
"proc-macro2",
854
-
"quote",
855
-
"syn 2.0.106",
856
-
]
857
-
858
-
[[package]]
859
name = "equivalent"
860
version = "1.0.2"
861
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1587
"chrono",
1588
"cid",
1589
"ed25519-dalek",
1590
-
"enum_dispatch",
1591
"http",
1592
"ipld-core",
1593
"k256",
+18
-1
Cargo.toml
+18
-1
Cargo.toml
···
5
6
[workspace.package]
7
edition = "2024"
8
-
version = "0.2.0"
9
authors = ["Orual <orual@nonbinary.computer>"]
10
repository = "https://tangled.org/@nonbinary.computer/jacquard"
11
keywords = ["atproto", "at", "bluesky", "api", "client"]
···
57
# HTTP
58
http = "1.3"
59
reqwest = { version = "0.12", default-features = false }
···
5
6
[workspace.package]
7
edition = "2024"
8
+
version = "0.3.0"
9
authors = ["Orual <orual@nonbinary.computer>"]
10
repository = "https://tangled.org/@nonbinary.computer/jacquard"
11
keywords = ["atproto", "at", "bluesky", "api", "client"]
···
57
# HTTP
58
http = "1.3"
59
reqwest = { version = "0.12", default-features = false }
60
+
61
+
# Async and runtimes
62
+
async-trait = "0.1"
63
+
tokio = "1"
64
+
65
+
# Encoding and crypto building blocks
66
+
base64 = "0.22"
67
+
percent-encoding = "2.3"
68
+
urlencoding = "2.1.3"
69
+
rand_core = "0.6"
70
+
71
+
# Time
72
+
chrono = "0.4"
73
+
74
+
# Crypto curves and JOSE
75
+
p256 = "0.13"
76
+
jose-jwk = "0.1"
+1
-1
crates/jacquard-api/Cargo.toml
+1
-1
crates/jacquard-api/Cargo.toml
+6
-8
crates/jacquard-common/Cargo.toml
+6
-8
crates/jacquard-common/Cargo.toml
···
13
14
15
[dependencies]
16
-
bon = "3"
17
-
base64 = "0.22.1"
18
bytes.workspace = true
19
-
chrono = "0.4.42"
20
cid = { version = "0.11.1", features = ["serde", "std"] }
21
-
enum_dispatch = "0.3.13"
22
ipld-core = { version = "0.4.2", features = ["serde"] }
23
langtag = { version = "0.4.0", features = ["serde"] }
24
miette.workspace = true
···
36
thiserror.workspace = true
37
url.workspace = true
38
http.workspace = true
39
-
async-trait = "0.1"
40
-
tokio = { version = "1", features = ["sync"] }
41
reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
42
serde_ipld_dagcbor.workspace = true
43
trait-variant.workspace = true
···
63
features = ["arithmetic"]
64
65
[dependencies.p256]
66
-
version = "0.13"
67
optional = true
68
-
default-features = false
69
features = ["arithmetic"]
70
71
[package.metadata.docs.rs]
···
13
14
15
[dependencies]
16
+
bon.workspace = true
17
+
base64.workspace = true
18
bytes.workspace = true
19
+
chrono.workspace = true
20
cid = { version = "0.11.1", features = ["serde", "std"] }
21
ipld-core = { version = "0.4.2", features = ["serde"] }
22
langtag = { version = "0.4.0", features = ["serde"] }
23
miette.workspace = true
···
35
thiserror.workspace = true
36
url.workspace = true
37
http.workspace = true
38
+
async-trait.workspace = true
39
+
tokio = { workspace = true, features = ["sync"] }
40
reqwest = { workspace = true, optional = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
41
serde_ipld_dagcbor.workspace = true
42
trait-variant.workspace = true
···
62
features = ["arithmetic"]
63
64
[dependencies.p256]
65
+
workspace = true
66
optional = true
67
features = ["arithmetic"]
68
69
[package.metadata.docs.rs]
+4
-4
crates/jacquard-identity/Cargo.toml
+4
-4
crates/jacquard-identity/Cargo.toml
···
16
dns = ["dep:hickory-resolver"]
17
18
[dependencies]
19
-
async-trait = "0.1.89"
20
bon.workspace = true
21
bytes.workspace = true
22
jacquard-common = { version = "0.2", path = "../jacquard-common" }
23
-
percent-encoding = "2.3.2"
24
reqwest.workspace = true
25
url.workspace = true
26
-
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
27
hickory-resolver = { optional = true, version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"]}
28
serde.workspace = true
29
serde_json.workspace = true
···
32
http.workspace = true
33
jacquard-api = { version = "0.2.0", path = "../jacquard-api" }
34
serde_html_form.workspace = true
35
-
urlencoding = "2.1.3"
···
16
dns = ["dep:hickory-resolver"]
17
18
[dependencies]
19
+
async-trait.workspace = true
20
bon.workspace = true
21
bytes.workspace = true
22
jacquard-common = { version = "0.2", path = "../jacquard-common" }
23
+
percent-encoding.workspace = true
24
reqwest.workspace = true
25
url.workspace = true
26
+
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
27
hickory-resolver = { optional = true, version = "0.24", default-features = false, features = ["system-config", "tokio-runtime"]}
28
serde.workspace = true
29
serde_json.workspace = true
···
32
http.workspace = true
33
jacquard-api = { version = "0.2.0", path = "../jacquard-api" }
34
serde_html_form.workspace = true
35
+
urlencoding.workspace = true
+9
-9
crates/jacquard-oauth/Cargo.toml
+9
-9
crates/jacquard-oauth/Cargo.toml
···
1
[package]
2
name = "jacquard-oauth"
3
-
version = "0.1.0"
4
edition = "2024"
5
description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard"
6
license = "MPL-2.0"
7
8
[dependencies]
9
-
jacquard-common = { version = "0.2.0", path = "../jacquard-common" }
10
serde = { workspace = true, features = ["derive"] }
11
serde_json = { workspace = true }
12
url = { workspace = true }
13
smol_str = { workspace = true }
14
-
base64 = { version = "0.22" }
15
sha2 = { version = "0.10" }
16
thiserror = { workspace = true }
17
serde_html_form = { workspace = true }
18
miette = { workspace = true }
19
uuid = { version = "1", features = ["v4","std"] }
20
-
p256 = { version = "0.13", features = ["ecdsa"] }
21
signature = "2"
22
-
rand_core = "0.6"
23
jose-jwa = "0.1"
24
-
jose-jwk = { version = "0.1", features = ["p256"] }
25
-
chrono = "0.4"
26
elliptic-curve = "0.13.8"
27
http.workspace = true
28
bytes.workspace = true
29
rand = { version = "0.8.5", features = ["small_rng"] }
30
-
async-trait = "0.1.89"
31
dashmap = "6.1.0"
32
-
tokio = { version = "1.47.1", features = ["sync"] }
33
34
reqwest.workspace = true
35
trait-variant.workspace = true
···
1
[package]
2
name = "jacquard-oauth"
3
+
version = "0.3.0"
4
edition = "2024"
5
description = "AT Protocol OAuth 2.1 core types and helpers for Jacquard"
6
license = "MPL-2.0"
7
8
[dependencies]
9
+
jacquard-common = { version = "0.3.0", path = "../jacquard-common" }
10
serde = { workspace = true, features = ["derive"] }
11
serde_json = { workspace = true }
12
url = { workspace = true }
13
smol_str = { workspace = true }
14
+
base64.workspace = true
15
sha2 = { version = "0.10" }
16
thiserror = { workspace = true }
17
serde_html_form = { workspace = true }
18
miette = { workspace = true }
19
uuid = { version = "1", features = ["v4","std"] }
20
+
p256 = { workspace = true, features = ["ecdsa"] }
21
signature = "2"
22
+
rand_core.workspace = true
23
jose-jwa = "0.1"
24
+
jose-jwk = { workspace = true, features = ["p256"] }
25
+
chrono.workspace = true
26
elliptic-curve = "0.13.8"
27
http.workspace = true
28
bytes.workspace = true
29
rand = { version = "0.8.5", features = ["small_rng"] }
30
+
async-trait.workspace = true
31
dashmap = "6.1.0"
32
+
tokio = { workspace = true, features = ["sync"] }
33
34
reqwest.workspace = true
35
trait-variant.workspace = true
+25
-11
crates/jacquard-oauth/src/request.rs
+25
-11
crates/jacquard-oauth/src/request.rs
···
43
#[derive(Error, Debug, miette::Diagnostic)]
44
pub enum RequestError {
45
#[error("no {0} endpoint available")]
46
-
#[diagnostic(code(jacquard_oauth::request::no_endpoint), help("server does not advertise this endpoint"))]
47
NoEndpoint(CowStr<'static>),
48
#[error("token response verification failed")]
49
#[diagnostic(code(jacquard_oauth::request::token_verification))]
···
51
#[error("unsupported authentication method")]
52
#[diagnostic(
53
code(jacquard_oauth::request::unsupported_auth_method),
54
-
help("server must support `private_key_jwt` or `none`; configure client metadata accordingly")
55
)]
56
UnsupportedAuthMethod,
57
#[error("no refresh token available")]
···
76
#[diagnostic(code(jacquard_oauth::request::http_build))]
77
Http(#[from] http::Error),
78
#[error("http status: {0}")]
79
-
#[diagnostic(code(jacquard_oauth::request::http_status), help("see server response for details"))]
80
HttpStatus(StatusCode),
81
#[error("http status: {0}, body: {1:?}")]
82
-
#[diagnostic(code(jacquard_oauth::request::http_status_body), help("server returned error JSON; inspect fields like `error`, `error_description`"))]
83
HttpStatusWithBody(StatusCode, Value),
84
#[error(transparent)]
85
#[diagnostic(code(jacquard_oauth::request::identity))]
···
134
mod tests {
135
use super::*;
136
use crate::types::{OAuthAuthorizationServerMetadata, OAuthClientMetadata};
137
-
use http::{Response as HttpResponse, StatusCode};
138
use bytes::Bytes;
139
use jacquard_common::http_client::HttpClient;
140
use jacquard_identity::resolver::IdentityResolver;
141
use std::sync::Arc;
···
249
async fn refresh_no_refresh_token() {
250
let client = MockClient::default();
251
let meta = base_metadata();
252
-
let mut session = ClientSessionData {
253
account_did: jacquard_common::types::string::Did::new_static("did:plc:alice").unwrap(),
254
session_id: CowStr::from("state"),
255
host_url: url::Url::parse("https://pds").unwrap(),
···
284
*client.resp.lock().await = Some(
285
HttpResponse::builder()
286
.status(StatusCode::OK)
287
-
.body(serde_json::to_vec(&serde_json::json!({
288
-
"access_token":"tok",
289
-
"token_type":"DPoP",
290
-
"expires_in": 3600
291
-
})).unwrap())
292
.unwrap(),
293
);
294
let meta = base_metadata();
···
43
#[derive(Error, Debug, miette::Diagnostic)]
44
pub enum RequestError {
45
#[error("no {0} endpoint available")]
46
+
#[diagnostic(
47
+
code(jacquard_oauth::request::no_endpoint),
48
+
help("server does not advertise this endpoint")
49
+
)]
50
NoEndpoint(CowStr<'static>),
51
#[error("token response verification failed")]
52
#[diagnostic(code(jacquard_oauth::request::token_verification))]
···
54
#[error("unsupported authentication method")]
55
#[diagnostic(
56
code(jacquard_oauth::request::unsupported_auth_method),
57
+
help(
58
+
"server must support `private_key_jwt` or `none`; configure client metadata accordingly"
59
+
)
60
)]
61
UnsupportedAuthMethod,
62
#[error("no refresh token available")]
···
81
#[diagnostic(code(jacquard_oauth::request::http_build))]
82
Http(#[from] http::Error),
83
#[error("http status: {0}")]
84
+
#[diagnostic(
85
+
code(jacquard_oauth::request::http_status),
86
+
help("see server response for details")
87
+
)]
88
HttpStatus(StatusCode),
89
#[error("http status: {0}, body: {1:?}")]
90
+
#[diagnostic(
91
+
code(jacquard_oauth::request::http_status_body),
92
+
help("server returned error JSON; inspect fields like `error`, `error_description`")
93
+
)]
94
HttpStatusWithBody(StatusCode, Value),
95
#[error(transparent)]
96
#[diagnostic(code(jacquard_oauth::request::identity))]
···
145
mod tests {
146
use super::*;
147
use crate::types::{OAuthAuthorizationServerMetadata, OAuthClientMetadata};
148
use bytes::Bytes;
149
+
use http::{Response as HttpResponse, StatusCode};
150
use jacquard_common::http_client::HttpClient;
151
use jacquard_identity::resolver::IdentityResolver;
152
use std::sync::Arc;
···
260
async fn refresh_no_refresh_token() {
261
let client = MockClient::default();
262
let meta = base_metadata();
263
+
let session = ClientSessionData {
264
account_did: jacquard_common::types::string::Did::new_static("did:plc:alice").unwrap(),
265
session_id: CowStr::from("state"),
266
host_url: url::Url::parse("https://pds").unwrap(),
···
295
*client.resp.lock().await = Some(
296
HttpResponse::builder()
297
.status(StatusCode::OK)
298
+
.body(
299
+
serde_json::to_vec(&serde_json::json!({
300
+
"access_token":"tok",
301
+
"token_type":"DPoP",
302
+
"expires_in": 3600
303
+
}))
304
+
.unwrap(),
305
+
)
306
.unwrap(),
307
);
308
let meta = base_metadata();
+12
-12
crates/jacquard/Cargo.toml
+12
-12
crates/jacquard/Cargo.toml
···
2
name = "jacquard"
3
description.workspace = true
4
edition.workspace = true
5
-
version = "0.2.1"
6
authors.workspace = true
7
repository.workspace = true
8
keywords.workspace = true
···
30
31
32
[dependencies]
33
-
bon = "3"
34
-
async-trait = "0.1"
35
bytes.workspace = true
36
clap.workspace = true
37
http.workspace = true
38
-
jacquard-api = { version = "0.2.0", path = "../jacquard-api" }
39
-
jacquard-common = { version = "0.2.0", path = "../jacquard-common", features = ["reqwest-client"] }
40
-
jacquard-oauth = { version = "0.1.0", path = "../jacquard-oauth" }
41
jacquard-derive = { version = "0.2.0", path = "../jacquard-derive", optional = true }
42
miette = { workspace = true }
43
reqwest = { workspace = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
···
46
serde_ipld_dagcbor.workspace = true
47
serde_json.workspace = true
48
thiserror.workspace = true
49
-
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
50
url.workspace = true
51
smol_str.workspace = true
52
-
percent-encoding = "2"
53
-
urlencoding = "2"
54
-
jose-jwk = { version = "0.1", features = ["p256"] }
55
-
p256 = { version = "0.13", features = ["ecdsa"] }
56
-
rand_core = "0.6"
57
rouille = { version = "3.6.2", optional = true }
58
jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
···
2
name = "jacquard"
3
description.workspace = true
4
edition.workspace = true
5
+
version = "0.3.0"
6
authors.workspace = true
7
repository.workspace = true
8
keywords.workspace = true
···
30
31
32
[dependencies]
33
+
bon.workspace = true
34
+
async-trait.workspace = true
35
bytes.workspace = true
36
clap.workspace = true
37
http.workspace = true
38
+
jacquard-api = { version = "0.3.0", path = "../jacquard-api" }
39
+
jacquard-common = { version = "0.3.0", path = "../jacquard-common", features = ["reqwest-client"] }
40
+
jacquard-oauth = { version = "0.3.0", path = "../jacquard-oauth" }
41
jacquard-derive = { version = "0.2.0", path = "../jacquard-derive", optional = true }
42
miette = { workspace = true }
43
reqwest = { workspace = true, features = ["charset", "http2", "json", "system-proxy", "gzip", "rustls-tls"] }
···
46
serde_ipld_dagcbor.workspace = true
47
serde_json.workspace = true
48
thiserror.workspace = true
49
+
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "fs"] }
50
url.workspace = true
51
smol_str.workspace = true
52
+
percent-encoding.workspace = true
53
+
urlencoding.workspace = true
54
+
jose-jwk = { workspace = true, features = ["p256"] }
55
+
p256 = { workspace = true, features = ["ecdsa"] }
56
+
rand_core.workspace = true
57
rouille = { version = "3.6.2", optional = true }
58
jacquard-identity = { version = "0.2.0", path = "../jacquard-identity" }
+1
-1
crates/jacquard/tests/oauth_flow.rs
+1
-1
crates/jacquard/tests/oauth_flow.rs
···
239
keyset: None,
240
};
241
let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social"));
242
-
let mut auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata)
243
.await
244
.unwrap();
245
// Construct authorization URL as OAuthClient::start_auth would do
···
239
keyset: None,
240
};
241
let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social"));
242
+
let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata)
243
.await
244
.unwrap();
245
// Construct authorization URL as OAuthClient::start_auth would do