-1
Cargo.lock
-1
Cargo.lock
-1
Cargo.toml
-1
Cargo.toml
+1
-1
crates/jacquard-common/Cargo.toml
+1
-1
crates/jacquard-common/Cargo.toml
···
93
93
futures-lite = "2.6"
94
94
95
95
[package.metadata.docs.rs]
96
-
features = [ "crypto-k256", "crypto-k256", "crypto-p256", "websocket", "zstd", "service-auth", "reqwest-client", "crypto"]
96
+
features = [ "crypto-k256", "crypto-ed22519", "crypto-p256", "websocket", "zstd", "service-auth", "reqwest-client", "crypto"]
-1
crates/jacquard-oauth/Cargo.toml
-1
crates/jacquard-oauth/Cargo.toml
+30
-21
crates/jacquard-oauth/src/atproto.rs
+30
-21
crates/jacquard-oauth/src/atproto.rs
···
152
152
#[derive(serde::Serialize)]
153
153
struct Parameters<'a> {
154
154
#[serde(skip_serializing_if = "Option::is_none")]
155
-
redirect_uri: Option<Vec<Url>>,
155
+
redirect_uri: Option<Vec<CowStr<'a>>>,
156
156
#[serde(skip_serializing_if = "Option::is_none")]
157
157
scope: Option<CowStr<'a>>,
158
158
}
159
159
let query = serde_html_form::to_string(Parameters {
160
-
redirect_uri: redirect_uris.clone(),
160
+
redirect_uri: redirect_uris.as_ref().map(|u| {
161
+
u.iter()
162
+
.map(|u| u.as_str().trim_end_matches("/").to_cowstr().into_static())
163
+
.collect()
164
+
}),
161
165
scope: scopes
162
166
.as_ref()
163
167
.map(|s| Scope::serialize_multiple(s.as_slice())),
164
168
})
165
169
.ok();
166
-
let mut client_id = String::from("http://localhost");
170
+
let mut client_id = String::from("http://localhost/");
167
171
if let Some(query) = query
168
172
&& !query.is_empty()
169
173
{
···
173
177
client_id: Url::parse(&client_id).unwrap(),
174
178
client_uri: None,
175
179
redirect_uris: redirect_uris.unwrap_or(vec![
176
-
Url::from_str("http://127.0.0.1/").unwrap(),
177
-
Url::from_str("http://[::1]/").unwrap(),
180
+
Url::from_str("http://127.0.0.1").unwrap(),
181
+
Url::from_str("http://[::1]").unwrap(),
178
182
]),
179
183
grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
180
184
scopes: scopes.unwrap_or(vec![Scope::Atproto]),
···
216
220
} else {
217
221
(AuthMethod::None, None, None)
218
222
};
219
-
223
+
let client_id = metadata.client_id.as_str().trim_end_matches("/");
224
+
let client_uri = metadata
225
+
.client_uri
226
+
.map(|u| u.as_str().trim_end_matches("/").to_cowstr().into_static());
227
+
let redirect_uris = metadata
228
+
.redirect_uris
229
+
.iter()
230
+
.map(|u| u.as_str().trim_end_matches("/").to_cowstr().into_static())
231
+
.collect();
232
+
let jwks_uri = jwks_uri.map(|u| u.as_str().trim_end_matches("/").to_cowstr().into_static());
220
233
Ok(OAuthClientMetadata {
221
-
client_id: metadata.client_id.to_cowstr().into_static(),
222
-
client_uri: metadata.client_uri.map(|u| u.to_cowstr().into_static()),
223
-
redirect_uris: metadata
224
-
.redirect_uris
225
-
.iter()
226
-
.map(|u| u.to_cowstr().into_static())
227
-
.collect(),
234
+
client_id: client_id.to_cowstr().into_static(),
235
+
client_uri,
236
+
redirect_uris,
228
237
token_endpoint_auth_method: Some(auth_method.into()),
229
238
grant_types: if keyset.is_some() {
230
239
Some(metadata.grant_types.into_iter().map(|v| v.into()).collect())
···
233
242
},
234
243
scope: Some(Scope::serialize_multiple(metadata.scopes.as_slice())),
235
244
dpop_bound_access_tokens: Some(true),
236
-
jwks_uri: jwks_uri.map(|u| u.to_cowstr().into_static()),
245
+
jwks_uri,
237
246
jwks,
238
247
token_endpoint_auth_signing_alg: if keyset.is_some() {
239
248
Some(CowStr::new_static("ES256"))
···
275
284
client_id: CowStr::new_static("http://localhost"),
276
285
client_uri: None,
277
286
redirect_uris: vec![
278
-
CowStr::new_static("http://127.0.0.1/"),
279
-
CowStr::new_static("http://[::1]/"),
287
+
CowStr::new_static("http://127.0.0.1"),
288
+
CowStr::new_static("http://[::1]"),
280
289
],
281
290
scope: Some(CowStr::new_static("atproto")),
282
291
grant_types: None,
···
313
322
.expect("failed to convert metadata"),
314
323
OAuthClientMetadata {
315
324
client_id: CowStr::new_static(
316
-
"http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&scope=account%3Aemail+atproto+transition%3Ageneric"
325
+
"http://localhost/?redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&scope=account%3Aemail+atproto+transition%3Ageneric"
317
326
),
318
327
client_uri: None,
319
328
redirect_uris: vec![
···
354
363
out,
355
364
OAuthClientMetadata {
356
365
client_id: CowStr::new_static(
357
-
"http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2F"
366
+
"http://localhost/?redirect_uri=http%3A%2F%2F127.0.0.1"
358
367
),
359
368
client_uri: None,
360
369
redirect_uris: vec![CowStr::new_static("http://127.0.0.1")],
···
385
394
out,
386
395
OAuthClientMetadata {
387
396
client_id: CowStr::new_static(
388
-
"http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A8000%2F"
397
+
"http://localhost/?redirect_uri=http%3A%2F%2F127.0.0.1%3A8000"
389
398
),
390
399
client_uri: None,
391
400
redirect_uris: vec![CowStr::new_static("http://127.0.0.1:8000")],
···
416
425
out,
417
426
OAuthClientMetadata {
418
427
client_id: CowStr::new_static(
419
-
"http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%2F"
428
+
"http://localhost/?redirect_uri=http%3A%2F%2F127.0.0.1"
420
429
),
421
430
client_uri: None,
422
-
redirect_uris: vec![CowStr::new_static("http://127.0.0.1/")],
431
+
redirect_uris: vec![CowStr::new_static("http://127.0.0.1")],
423
432
scope: Some(CowStr::new_static("atproto")),
424
433
grant_types: None,
425
434
token_endpoint_auth_method: Some(AuthMethod::None.into()),
+1
-1
crates/jacquard-oauth/src/client.rs
+1
-1
crates/jacquard-oauth/src/client.rs
···
593
593
594
594
async fn set_base_uri(&self, url: Url) {
595
595
let mut guard = self.data.write().await;
596
-
guard.host_url = url.to_cowstr().into_static();
596
+
guard.host_url = url.as_str().trim_end_matches("/").to_cowstr().into_static();
597
597
}
598
598
599
599
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
+6
-3
crates/jacquard-oauth/src/resolver.rs
+6
-3
crates/jacquard-oauth/src/resolver.rs
···
5
5
use http::{Request, StatusCode};
6
6
use jacquard_common::CowStr;
7
7
use jacquard_common::IntoStatic;
8
+
use jacquard_common::cowstr::ToCowStr;
8
9
use jacquard_common::types::did_doc::DidDocument;
9
10
use jacquard_common::types::ident::AtIdentifier;
10
11
use jacquard_common::{http_client::HttpClient, types::did::Did};
···
423
424
// resolve to a DID)
424
425
Ok(if input.starts_with("https://") {
425
426
let url = Url::parse(input).map_err(|_| ResolverError::not_found())?;
426
-
(resolver.resolve_from_service(&url).await?, None)
427
+
(resolver.resolve_from_service(&url.to_cowstr()).await?, None)
427
428
} else {
428
429
let (metadata, identity) = resolver.resolve_from_identity(input).await?;
429
430
(metadata, Some(identity))
···
491
492
.map_err(|e| ResolverError::at_identifier(smol_str::format_smolstr!("{:?}", e)))?;
492
493
let identity = resolver.resolve_ident_owned(&actor).await?;
493
494
if let Some(pds) = &identity.pds_endpoint() {
494
-
let metadata = resolver.get_resource_server_metadata(pds).await?;
495
+
let metadata = resolver
496
+
.get_resource_server_metadata(&pds.to_cowstr())
497
+
.await?;
495
498
Ok((metadata, identity))
496
499
} else {
497
500
Err(ResolverError::did_document("Did doc lacking pds"))
···
514
517
issuer: &CowStr<'_>,
515
518
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
516
519
let mut md = resolve_authorization_server(client, issuer).await?;
517
-
md.issuer = issuer.into_static();
520
+
md.issuer = issuer.clone().into_static();
518
521
Ok(md)
519
522
}
520
523
+1
-1
crates/jacquard/Cargo.toml
+1
-1
crates/jacquard/Cargo.toml
···
144
144
bytes.workspace = true
145
145
http.workspace = true
146
146
miette = { workspace = true }
147
-
reqwest = { workspace = true, features = ["charset", "json", "gzip"] }
147
+
reqwest = { workspace = true, features = ["json", "gzip"] }
148
148
serde.workspace = true
149
149
serde_html_form.workspace = true
150
150
serde_json.workspace = true
+3
-2
crates/jacquard/src/client.rs
+3
-2
crates/jacquard/src/client.rs
···
254
254
let base_uri = self.base_uri().await;
255
255
let base_uri = Url::parse(&base_uri).expect("base_uri should be valid url");
256
256
self.resolver
257
-
.xrpc(base_uri.clone())
257
+
.xrpc(base_uri)
258
258
.with_options(opts.clone())
259
259
.send(&request)
260
260
.await
···
287
287
{
288
288
async move {
289
289
let base_uri = self.base_uri().await;
290
+
let base_uri = Url::parse(&base_uri).expect("base_uri should be valid url");
290
291
self.resolver
291
-
.xrpc(base_uri.clone())
292
+
.xrpc(base_uri)
292
293
.with_options(opts.clone())
293
294
.send(&request)
294
295
.await
+3
-2
crates/jacquard/src/client/credential_session.rs
+3
-2
crates/jacquard/src/client/credential_session.rs
···
286
286
}
287
287
// Activate
288
288
*self.key.write().await = Some(key);
289
-
*self.endpoint.write().await = Some(pds.to_cowstr().into_static());
289
+
*self.endpoint.write().await =
290
+
Some(pds.as_str().trim_end_matches("/").to_cowstr().into_static());
290
291
291
292
Ok(session)
292
293
}
···
442
443
443
444
async fn set_base_uri(&self, url: Url) {
444
445
let mut guard = self.endpoint.write().await;
445
-
*guard = Some(url.to_cowstr().into_static());
446
+
*guard = Some(url.as_str().trim_end_matches("/").to_cowstr().into_static());
446
447
}
447
448
448
449
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
+2
-5
crates/jacquard/tests/credential_session.rs
+2
-5
crates/jacquard/tests/credential_session.rs
···
169
169
let session = CredentialSession::new(store.clone(), client.clone());
170
170
171
171
// Before login, default endpoint should be public appview
172
-
assert_eq!(
173
-
session.endpoint().await.as_str(),
174
-
"https://public.bsky.app/"
175
-
);
172
+
assert_eq!(session.endpoint().await.as_str(), "https://public.bsky.app");
176
173
177
174
// Login using handle; resolves to PDS and persists session
178
175
session
···
187
184
.expect("login ok");
188
185
189
186
// Endpoint switches to PDS
190
-
assert_eq!(session.endpoint().await.as_str(), "https://pds/");
187
+
assert_eq!(session.endpoint().await.as_str(), "https://pds");
191
188
192
189
// Send a request that will first 401 (ExpiredToken), then refresh, then succeed
193
190
let resp = session