CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(oauth-client): add JWKS stage integration tests with AC3 coverage

Implements Phase 5 Task 5: complete JWKS stage integration testing with all
10 required tests covering AC3.1 through AC3.8 plus additional tests for
discovery failures and loopback clients.

Tests created:
- inline_es256_happy_jwks_passes (AC3.1)
- uri_es256_happy_jwks_passes (AC3.2)
- uri_unreachable_produces_network_error (AC3.3)
- duplicate_kids_produces_spec_violation (AC3.4)
- missing_alg_produces_spec_violation (AC3.5)
- wrong_use_produces_spec_violation (AC3.6)
- weak_alg_rs1_produces_spec_violation (AC3.7)
- public_client_skips_all_jwks (AC3.8)
- discovery_failure_blocks_jwks
- loopback_skips_all_jwks

Fixtures created under tests/fixtures/oauth_client/jwks/:
- inline_es256_happy/metadata.json (inline JWKS with ES256)
- uri_es256_happy/{metadata.json,jwks.json} (JWKS via URI)
- uri_unreachable/metadata.json (JWKS URI transport error)
- duplicate_kids/metadata.json (duplicate kid check failure)
- missing_alg/metadata.json (missing alg check failure)
- wrong_use/metadata.json (wrong use check failure)
- weak_alg_rs1/metadata.json (weak algorithm check failure)
- public_client_skipped/metadata.json (public client skips all checks)

All 10 tests pass. Snapshots pinned via insta. No regressions in existing tests.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

+858
+32
tests/fixtures/oauth_client/jwks/duplicate_kids/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + }, 21 + { 22 + "kty": "EC", 23 + "crv": "P-256", 24 + "alg": "ES256", 25 + "use": "sig", 26 + "kid": "k1", 27 + "x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", 28 + "y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKcE" 29 + } 30 + ] 31 + } 32 + }
+23
tests/fixtures/oauth_client/jwks/inline_es256_happy/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+22
tests/fixtures/oauth_client/jwks/missing_alg/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "use": "sig", 16 + "kid": "k1", 17 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 18 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 19 + } 20 + ] 21 + } 22 + }
+10
tests/fixtures/oauth_client/jwks/public_client_skipped/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "none" 10 + }
+13
tests/fixtures/oauth_client/jwks/uri_es256_happy/jwks.json
··· 1 + { 2 + "keys": [ 3 + { 4 + "kty": "EC", 5 + "crv": "P-256", 6 + "alg": "ES256", 7 + "use": "sig", 8 + "kid": "k1", 9 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 10 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 11 + } 12 + ] 13 + }
+11
tests/fixtures/oauth_client/jwks/uri_es256_happy/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks_uri": "https://client.example.com/jwks.json" 11 + }
+11
tests/fixtures/oauth_client/jwks/uri_unreachable/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks_uri": "https://client.example.com/jwks.json" 11 + }
+23
tests/fixtures/oauth_client/jwks/weak_alg_rs1/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "secp256k1", 15 + "alg": "RS1", 16 + "use": "sig", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+23
tests/fixtures/oauth_client/jwks/wrong_use/metadata.json
··· 1 + { 2 + "client_id": "https://client.example.com/metadata.json", 3 + "application_type": "web", 4 + "redirect_uris": ["https://client.example.com/callback"], 5 + "grant_types": ["authorization_code"], 6 + "response_types": ["code"], 7 + "scope": "atproto", 8 + "dpop_bound_access_tokens": true, 9 + "token_endpoint_auth_method": "private_key_jwt", 10 + "jwks": { 11 + "keys": [ 12 + { 13 + "kty": "EC", 14 + "crv": "P-256", 15 + "alg": "ES256", 16 + "use": "enc", 17 + "kid": "k1", 18 + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 19 + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" 20 + } 21 + ] 22 + } 23 + }
+310
tests/oauth_client_jwks.rs
··· 1 + //! Integration tests for the oauth client JWKS stage using snapshot tests. 2 + 3 + mod common; 4 + 5 + use atproto_devtool::commands::test::oauth::client::pipeline::{ 6 + OauthClientOptions, OauthClientReport, parse_target, run_pipeline, 7 + }; 8 + use atproto_devtool::common::report::RenderConfig; 9 + use url::Url; 10 + 11 + /// Helper to render a report to a string. 12 + fn render_report_to_string(report: &OauthClientReport) -> String { 13 + let mut buf = Vec::new(); 14 + report 15 + .render(&mut buf, &RenderConfig { no_color: true }) 16 + .expect("render failed"); 17 + String::from_utf8(buf).expect("invalid utf-8") 18 + } 19 + 20 + // ============================================================================= 21 + // AC3.1: Inline ES256 happy path 22 + // ============================================================================= 23 + 24 + #[tokio::test] 25 + async fn inline_es256_happy_jwks_passes() { 26 + let http = common::FakeHttpClient::new(); 27 + let metadata = include_bytes!("fixtures/oauth_client/jwks/inline_es256_happy/metadata.json"); 28 + http.add_response( 29 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 30 + 200, 31 + metadata.to_vec(), 32 + ); 33 + 34 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 35 + let jwks_fetcher = common::FakeJwksFetcher::new(); 36 + let opts = OauthClientOptions { 37 + http: &http, 38 + jwks: &jwks_fetcher, 39 + verbose: false, 40 + }; 41 + 42 + let report = run_pipeline(target, opts).await; 43 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass"); 44 + 45 + let rendered = render_report_to_string(&report); 46 + insta::assert_snapshot!(rendered); 47 + } 48 + 49 + // ============================================================================= 50 + // AC3.2: JWKS URI with ES256 happy path 51 + // ============================================================================= 52 + 53 + #[tokio::test] 54 + async fn uri_es256_happy_jwks_passes() { 55 + let http = common::FakeHttpClient::new(); 56 + let metadata = include_bytes!("fixtures/oauth_client/jwks/uri_es256_happy/metadata.json"); 57 + http.add_response( 58 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 59 + 200, 60 + metadata.to_vec(), 61 + ); 62 + 63 + let jwks_uri = Url::parse("https://client.example.com/jwks.json").unwrap(); 64 + let jwks_fetcher = common::FakeJwksFetcher::new(); 65 + let jwks_bytes = include_bytes!("fixtures/oauth_client/jwks/uri_es256_happy/jwks.json"); 66 + jwks_fetcher.add_response(&jwks_uri, 200, jwks_bytes.to_vec(), None); 67 + 68 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 69 + let opts = OauthClientOptions { 70 + http: &http, 71 + jwks: &jwks_fetcher, 72 + verbose: false, 73 + }; 74 + 75 + let report = run_pipeline(target, opts).await; 76 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass"); 77 + 78 + let rendered = render_report_to_string(&report); 79 + insta::assert_snapshot!(rendered); 80 + } 81 + 82 + // ============================================================================= 83 + // AC3.3: JWKS URI unreachable produces network error 84 + // ============================================================================= 85 + 86 + #[tokio::test] 87 + async fn uri_unreachable_produces_network_error() { 88 + let http = common::FakeHttpClient::new(); 89 + let metadata = include_bytes!("fixtures/oauth_client/jwks/uri_unreachable/metadata.json"); 90 + http.add_response( 91 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 92 + 200, 93 + metadata.to_vec(), 94 + ); 95 + 96 + let jwks_uri = Url::parse("https://client.example.com/jwks.json").unwrap(); 97 + let jwks_fetcher = common::FakeJwksFetcher::new(); 98 + jwks_fetcher.add_transport_error(&jwks_uri, "connection refused"); 99 + 100 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 101 + let opts = OauthClientOptions { 102 + http: &http, 103 + jwks: &jwks_fetcher, 104 + verbose: false, 105 + }; 106 + 107 + let report = run_pipeline(target, opts).await; 108 + assert_eq!(report.exit_code(), 2, "Expected network error exit code"); 109 + 110 + let rendered = render_report_to_string(&report); 111 + insta::assert_snapshot!(rendered); 112 + } 113 + 114 + // ============================================================================= 115 + // AC3.4: Duplicate key IDs produce spec violation 116 + // ============================================================================= 117 + 118 + #[tokio::test] 119 + async fn duplicate_kids_produces_spec_violation() { 120 + let http = common::FakeHttpClient::new(); 121 + let metadata = include_bytes!("fixtures/oauth_client/jwks/duplicate_kids/metadata.json"); 122 + http.add_response( 123 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 124 + 200, 125 + metadata.to_vec(), 126 + ); 127 + 128 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 129 + let jwks_fetcher = common::FakeJwksFetcher::new(); 130 + let opts = OauthClientOptions { 131 + http: &http, 132 + jwks: &jwks_fetcher, 133 + verbose: false, 134 + }; 135 + 136 + let report = run_pipeline(target, opts).await; 137 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 138 + 139 + let rendered = render_report_to_string(&report); 140 + insta::assert_snapshot!(rendered); 141 + } 142 + 143 + // ============================================================================= 144 + // AC3.5: Missing alg field produces spec violation 145 + // ============================================================================= 146 + 147 + #[tokio::test] 148 + async fn missing_alg_produces_spec_violation() { 149 + let http = common::FakeHttpClient::new(); 150 + let metadata = include_bytes!("fixtures/oauth_client/jwks/missing_alg/metadata.json"); 151 + http.add_response( 152 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 153 + 200, 154 + metadata.to_vec(), 155 + ); 156 + 157 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 158 + let jwks_fetcher = common::FakeJwksFetcher::new(); 159 + let opts = OauthClientOptions { 160 + http: &http, 161 + jwks: &jwks_fetcher, 162 + verbose: false, 163 + }; 164 + 165 + let report = run_pipeline(target, opts).await; 166 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 167 + 168 + let rendered = render_report_to_string(&report); 169 + insta::assert_snapshot!(rendered); 170 + } 171 + 172 + // ============================================================================= 173 + // AC3.6: Wrong use field produces spec violation 174 + // ============================================================================= 175 + 176 + #[tokio::test] 177 + async fn wrong_use_produces_spec_violation() { 178 + let http = common::FakeHttpClient::new(); 179 + let metadata = include_bytes!("fixtures/oauth_client/jwks/wrong_use/metadata.json"); 180 + http.add_response( 181 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 182 + 200, 183 + metadata.to_vec(), 184 + ); 185 + 186 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 187 + let jwks_fetcher = common::FakeJwksFetcher::new(); 188 + let opts = OauthClientOptions { 189 + http: &http, 190 + jwks: &jwks_fetcher, 191 + verbose: false, 192 + }; 193 + 194 + let report = run_pipeline(target, opts).await; 195 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 196 + 197 + let rendered = render_report_to_string(&report); 198 + insta::assert_snapshot!(rendered); 199 + } 200 + 201 + // ============================================================================= 202 + // AC3.7: Weak algorithm produces spec violation 203 + // ============================================================================= 204 + 205 + #[tokio::test] 206 + async fn weak_alg_rs1_produces_spec_violation() { 207 + let http = common::FakeHttpClient::new(); 208 + let metadata = include_bytes!("fixtures/oauth_client/jwks/weak_alg_rs1/metadata.json"); 209 + http.add_response( 210 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 211 + 200, 212 + metadata.to_vec(), 213 + ); 214 + 215 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 216 + let jwks_fetcher = common::FakeJwksFetcher::new(); 217 + let opts = OauthClientOptions { 218 + http: &http, 219 + jwks: &jwks_fetcher, 220 + verbose: false, 221 + }; 222 + 223 + let report = run_pipeline(target, opts).await; 224 + assert_eq!(report.exit_code(), 1, "Expected SpecViolation exit code"); 225 + 226 + let rendered = render_report_to_string(&report); 227 + insta::assert_snapshot!(rendered); 228 + } 229 + 230 + // ============================================================================= 231 + // AC3.8: Public client skips all JWKS checks 232 + // ============================================================================= 233 + 234 + #[tokio::test] 235 + async fn public_client_skips_all_jwks() { 236 + let http = common::FakeHttpClient::new(); 237 + let metadata = include_bytes!("fixtures/oauth_client/jwks/public_client_skipped/metadata.json"); 238 + http.add_response( 239 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 240 + 200, 241 + metadata.to_vec(), 242 + ); 243 + 244 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 245 + let jwks_fetcher = common::FakeJwksFetcher::new(); 246 + let opts = OauthClientOptions { 247 + http: &http, 248 + jwks: &jwks_fetcher, 249 + verbose: false, 250 + }; 251 + 252 + let report = run_pipeline(target, opts).await; 253 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass or skip"); 254 + 255 + let rendered = render_report_to_string(&report); 256 + insta::assert_snapshot!(rendered); 257 + } 258 + 259 + // ============================================================================= 260 + // Additional test: Discovery failure blocks JWKS stage 261 + // ============================================================================= 262 + 263 + #[tokio::test] 264 + async fn discovery_failure_blocks_jwks() { 265 + let http = common::FakeHttpClient::new(); 266 + // Seed a 404 response 267 + http.add_response( 268 + &Url::parse("https://client.example.com/metadata.json").unwrap(), 269 + 404, 270 + b"not found".to_vec(), 271 + ); 272 + 273 + let target = parse_target("https://client.example.com/metadata.json").expect("parse failed"); 274 + let jwks_fetcher = common::FakeJwksFetcher::new(); 275 + let opts = OauthClientOptions { 276 + http: &http, 277 + jwks: &jwks_fetcher, 278 + verbose: false, 279 + }; 280 + 281 + let report = run_pipeline(target, opts).await; 282 + assert_eq!(report.exit_code(), 2, "Expected network error exit code"); 283 + 284 + let rendered = render_report_to_string(&report); 285 + insta::assert_snapshot!(rendered); 286 + } 287 + 288 + // ============================================================================= 289 + // Additional test: Loopback target skips all JWKS checks 290 + // ============================================================================= 291 + 292 + #[tokio::test] 293 + async fn loopback_skips_all_jwks() { 294 + let http = common::FakeHttpClient::new(); 295 + // No seeded responses needed for loopback 296 + 297 + let target = parse_target("http://localhost/").expect("parse failed"); 298 + let jwks_fetcher = common::FakeJwksFetcher::new(); 299 + let opts = OauthClientOptions { 300 + http: &http, 301 + jwks: &jwks_fetcher, 302 + verbose: false, 303 + }; 304 + 305 + let report = run_pipeline(target, opts).await; 306 + assert_eq!(report.exit_code(), 0, "Expected all checks to pass or skip"); 307 + 308 + let rendered = render_report_to_string(&report); 309 + insta::assert_snapshot!(rendered); 310 + }
+40
tests/snapshots/oauth_client_jwks__discovery_failure_blocks_jwks.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [NET] Metadata document unreachable 11 + oauth_client::discovery::metadata_document_fetchable 12 + 13 + × Metadata document fetch returned HTTP 404: https://client.example.com/metadata.json 14 + [SKIP] Metadata is valid JSON — blocked by oauth_client::discovery::metadata_document_fetchable 15 + == Metadata == 16 + [SKIP] Metadata document deserializes — blocked by oauth_client::discovery::metadata_document_fetchable 17 + [SKIP] Metadata `client_id` matches fetched URL — blocked by oauth_client::discovery::metadata_document_fetchable 18 + [SKIP] `application_type` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 19 + [SKIP] `application_type` is `web` or `native` — blocked by oauth_client::discovery::metadata_document_fetchable 20 + [SKIP] `response_types` is `["code"]` — blocked by oauth_client::discovery::metadata_document_fetchable 21 + [SKIP] `grant_types` includes `authorization_code` — blocked by oauth_client::discovery::metadata_document_fetchable 22 + [SKIP] `dpop_bound_access_tokens` is `true` — blocked by oauth_client::discovery::metadata_document_fetchable 23 + [SKIP] `redirect_uris` is non-empty — blocked by oauth_client::discovery::metadata_document_fetchable 24 + [SKIP] Every `redirect_uri` has the right shape for the client kind — blocked by oauth_client::discovery::metadata_document_fetchable 25 + [SKIP] `token_endpoint_auth_method` matches client kind — blocked by oauth_client::discovery::metadata_document_fetchable 26 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 27 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — blocked by oauth_client::discovery::metadata_document_fetchable 28 + [SKIP] `scope` field is present — blocked by oauth_client::discovery::metadata_document_fetchable 29 + [SKIP] `scope` includes the `atproto` token — blocked by oauth_client::discovery::metadata_document_fetchable 30 + [SKIP] `scope` parses against the atproto permission grammar — blocked by oauth_client::discovery::metadata_document_fetchable 31 + == JWKS == 32 + [SKIP] JWKS is present — blocked by oauth_client::metadata::raw_document_deserializes 33 + [SKIP] JWKS URI is fetchable — blocked by oauth_client::metadata::raw_document_deserializes 34 + [SKIP] JWKS is valid JSON — blocked by oauth_client::metadata::raw_document_deserializes 35 + [SKIP] Keys have unique kid values — blocked by oauth_client::metadata::raw_document_deserializes 36 + [SKIP] Keys declare alg field — blocked by oauth_client::metadata::raw_document_deserializes 37 + [SKIP] Keys use signing use — blocked by oauth_client::metadata::raw_document_deserializes 38 + [SKIP] Algorithms are modern EC — blocked by oauth_client::metadata::raw_document_deserializes 39 + 40 + Summary: 1 passed, 0 failed (spec), 1 network errors, 0 advisories, 23 skipped. Exit code: 2
+39
tests/snapshots/oauth_client_jwks__duplicate_kids_produces_spec_violation.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [SKIP] JWKS URI is fetchable — jwks is inline 30 + [OK] JWKS is valid JSON 31 + [OK] Keys declare alg field 32 + [OK] Keys use signing use 33 + [OK] Algorithms are modern EC 34 + [FAIL] Keys have unique kid values 35 + oauth_client::jws::jwks_json 36 + 37 + × Two or more keys share the same `kid` value 38 + 39 + Summary: 22 passed, 1 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 1
+36
tests/snapshots/oauth_client_jwks__inline_es256_happy_jwks_passes.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [SKIP] JWKS URI is fetchable — jwks is inline 30 + [OK] JWKS is valid JSON 31 + [OK] Keys declare alg field 32 + [OK] Keys use signing use 33 + [OK] Algorithms are modern EC 34 + [OK] Keys have unique kid values 35 + 36 + Summary: 23 passed, 0 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 0
+37
tests/snapshots/oauth_client_jwks__loopback_skips_all_jwks.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: http://localhost/ 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [SKIP] Metadata document fetchable — metadata is implicit for loopback clients 11 + [SKIP] Metadata is valid JSON — metadata is implicit for loopback clients 12 + == Metadata == 13 + [SKIP] Metadata document deserializes — metadata is implicit for loopback clients 14 + [SKIP] Metadata `client_id` matches fetched URL — metadata is implicit for loopback clients 15 + [SKIP] `application_type` field is present — metadata is implicit for loopback clients 16 + [SKIP] `application_type` is `web` or `native` — metadata is implicit for loopback clients 17 + [SKIP] `response_types` is `["code"]` — metadata is implicit for loopback clients 18 + [SKIP] `grant_types` includes `authorization_code` — metadata is implicit for loopback clients 19 + [SKIP] `dpop_bound_access_tokens` is `true` — metadata is implicit for loopback clients 20 + [SKIP] `redirect_uris` is non-empty — metadata is implicit for loopback clients 21 + [SKIP] Every `redirect_uri` has the right shape for the client kind — metadata is implicit for loopback clients 22 + [SKIP] `token_endpoint_auth_method` matches client kind — metadata is implicit for loopback clients 23 + [SKIP] Confidential client provides exactly one of `jwks`/`jwks_uri` — metadata is implicit for loopback clients 24 + [SKIP] Public/native client does not provide `jwks` or `jwks_uri` — metadata is implicit for loopback clients 25 + [SKIP] `scope` field is present — metadata is implicit for loopback clients 26 + [SKIP] `scope` includes the `atproto` token — metadata is implicit for loopback clients 27 + [SKIP] `scope` parses against the atproto permission grammar — metadata is implicit for loopback clients 28 + == JWKS == 29 + [SKIP] JWKS is present — jwks not applicable to loopback clients 30 + [SKIP] JWKS URI is fetchable — jwks not applicable to loopback clients 31 + [SKIP] JWKS is valid JSON — jwks not applicable to loopback clients 32 + [SKIP] Keys have unique kid values — jwks not applicable to loopback clients 33 + [SKIP] Keys declare alg field — jwks not applicable to loopback clients 34 + [SKIP] Keys use signing use — jwks not applicable to loopback clients 35 + [SKIP] Algorithms are modern EC — jwks not applicable to loopback clients 36 + 37 + Summary: 1 passed, 0 failed (spec), 0 network errors, 0 advisories, 24 skipped. Exit code: 0
+39
tests/snapshots/oauth_client_jwks__missing_alg_produces_spec_violation.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [SKIP] JWKS URI is fetchable — jwks is inline 30 + [OK] JWKS is valid JSON 31 + [FAIL] Keys declare alg field 32 + oauth_client::jws::jwks_json 33 + 34 + × One or more keys missing required `alg` field 35 + [OK] Keys use signing use 36 + [OK] Algorithms are modern EC 37 + [OK] Keys have unique kid values 38 + 39 + Summary: 22 passed, 1 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 1
+36
tests/snapshots/oauth_client_jwks__public_client_skips_all_jwks.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Public/native client does not provide `jwks` or `jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [SKIP] JWKS is present — jwks not required for public clients 29 + [SKIP] JWKS URI is fetchable — jwks not required for public clients 30 + [SKIP] JWKS is valid JSON — jwks not required for public clients 31 + [SKIP] Keys have unique kid values — jwks not required for public clients 32 + [SKIP] Keys declare alg field — jwks not required for public clients 33 + [SKIP] Keys use signing use — jwks not required for public clients 34 + [SKIP] Algorithms are modern EC — jwks not required for public clients 35 + 36 + Summary: 17 passed, 0 failed (spec), 0 network errors, 0 advisories, 7 skipped. Exit code: 0
+36
tests/snapshots/oauth_client_jwks__uri_es256_happy_jwks_passes.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [OK] JWKS URI is fetchable 30 + [OK] JWKS is valid JSON 31 + [OK] Keys declare alg field 32 + [OK] Keys use signing use 33 + [OK] Algorithms are modern EC 34 + [OK] Keys have unique kid values 35 + 36 + Summary: 24 passed, 0 failed (spec), 0 network errors, 0 advisories, 0 skipped. Exit code: 0
+39
tests/snapshots/oauth_client_jwks__uri_unreachable_produces_network_error.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [NET] JWKS URI is fetchable 30 + oauth_client::jws::jwks_uri_unreachable 31 + 32 + × network error fetching JWKS at `https://client.example.com/jwks.json`: connection refused 33 + [SKIP] JWKS is valid JSON — blocked by oauth_client::jws::jwks_uri_fetchable 34 + [SKIP] Keys have unique kid values — blocked by oauth_client::jws::jwks_uri_fetchable 35 + [SKIP] Keys declare alg field — blocked by oauth_client::jws::jwks_uri_fetchable 36 + [SKIP] Keys use signing use — blocked by oauth_client::jws::jwks_uri_fetchable 37 + [SKIP] Algorithms are modern EC — blocked by oauth_client::jws::jwks_uri_fetchable 38 + 39 + Summary: 18 passed, 0 failed (spec), 1 network errors, 0 advisories, 5 skipped. Exit code: 2
+39
tests/snapshots/oauth_client_jwks__weak_alg_rs1_produces_spec_violation.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [SKIP] JWKS URI is fetchable — jwks is inline 30 + [OK] JWKS is valid JSON 31 + [OK] Keys declare alg field 32 + [OK] Keys use signing use 33 + [FAIL] Algorithms are modern EC 34 + oauth_client::jws::jwks_json 35 + 36 + × One or more keys declare non-modern algorithms 37 + [OK] Keys have unique kid values 38 + 39 + Summary: 22 passed, 1 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 1
+39
tests/snapshots/oauth_client_jwks__wrong_use_produces_spec_violation.snap
··· 1 + --- 2 + source: tests/oauth_client_jwks.rs 3 + expression: rendered 4 + --- 5 + Target: https://client.example.com/metadata.json 6 + elapsed: 0ms 7 + 8 + == Discovery == 9 + [OK] Client ID well-formed 10 + [OK] Metadata document fetchable 11 + [OK] Metadata is valid JSON 12 + == Metadata == 13 + [OK] Metadata document deserializes 14 + [OK] Metadata `client_id` matches fetched URL 15 + [OK] `application_type` field is present 16 + [OK] `application_type` is `web` or `native` 17 + [OK] `response_types` is `["code"]` 18 + [OK] `grant_types` includes `authorization_code` 19 + [OK] `dpop_bound_access_tokens` is `true` 20 + [OK] `redirect_uris` is non-empty 21 + [OK] Every `redirect_uri` has the right shape for the client kind 22 + [OK] `token_endpoint_auth_method` matches client kind 23 + [OK] Confidential client provides exactly one of `jwks`/`jwks_uri` 24 + [OK] `scope` field is present 25 + [OK] `scope` includes the `atproto` token 26 + [OK] `scope` parses against the atproto permission grammar 27 + == JWKS == 28 + [OK] JWKS is present 29 + [SKIP] JWKS URI is fetchable — jwks is inline 30 + [OK] JWKS is valid JSON 31 + [OK] Keys declare alg field 32 + [FAIL] Keys use signing use 33 + oauth_client::jws::jwks_json 34 + 35 + × One or more keys have `use` other than `sig` 36 + [OK] Algorithms are modern EC 37 + [OK] Keys have unique kid values 38 + 39 + Summary: 22 passed, 1 failed (spec), 0 network errors, 0 advisories, 1 skipped. Exit code: 1