1use bitwarden_core::Client;
2#[cfg(feature = "wasm")]
3use wasm_bindgen::prelude::*;
4
5use crate::send_access::{
6 SendAccessTokenError, SendAccessTokenRequest, SendAccessTokenResponse,
7 access_token_response::UnexpectedIdentityError,
8 api::{
9 SendAccessTokenApiErrorResponse, SendAccessTokenApiSuccessResponse,
10 SendAccessTokenRequestPayload,
11 },
12};
13
14#[derive(Clone)]
16#[cfg_attr(feature = "wasm", wasm_bindgen)]
17pub struct SendAccessClient {
18 pub(crate) client: Client,
19}
20
21impl SendAccessClient {
22 pub(crate) fn new(client: Client) -> Self {
23 Self { client }
24 }
25}
26
27#[cfg_attr(feature = "wasm", wasm_bindgen)]
28impl SendAccessClient {
29 pub async fn request_send_access_token(
31 &self,
32 request: SendAccessTokenRequest,
33 ) -> Result<SendAccessTokenResponse, SendAccessTokenError> {
34 let payload: SendAccessTokenRequestPayload = request.into();
36
37 let configurations = self.client.internal.get_api_configurations().await;
44
45 let url = format!(
47 "{}/connect/token",
48 &configurations.identity_config.base_path
49 );
50
51 let request: reqwest::RequestBuilder = configurations
52 .identity_config
53 .client
54 .post(&url)
55 .header(reqwest::header::ACCEPT, "application/json")
56 .header(reqwest::header::CACHE_CONTROL, "no-store")
57 .form(&payload);
58
59 let response: reqwest::Response = request.send().await?;
64
65 let response_status = response.status();
66
67 if response_status.is_success() {
70 let send_access_token: SendAccessTokenApiSuccessResponse = response.json().await?;
71 return Ok(send_access_token.into());
72 }
73
74 let err_response = match response.json::<SendAccessTokenApiErrorResponse>().await {
75 Ok(err) => err,
79 Err(_) => {
80 let error_string = format!(
84 "Received response status {} against {}",
85 response_status, url
86 );
87
88 return Err(SendAccessTokenError::Unexpected(UnexpectedIdentityError(
89 error_string,
90 )));
91 }
92 };
93
94 Err(SendAccessTokenError::Expected(err_response))
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use bitwarden_core::{Client as CoreClient, ClientSettings, DeviceType};
101 use bitwarden_test::start_api_mock;
102 use wiremock::{
103 Mock, MockServer, ResponseTemplate,
104 matchers::{self, body_string_contains},
105 };
106
107 use crate::{
108 AuthClientExt,
109 api::enums::{GrantType, Scope},
110 send_access::{
111 SendAccessClient, SendAccessCredentials, SendAccessTokenError, SendAccessTokenRequest,
112 SendAccessTokenResponse, SendEmailCredentials, SendEmailOtpCredentials,
113 SendPasswordCredentials, UnexpectedIdentityError,
114 api::{
115 SendAccessTokenApiErrorResponse, SendAccessTokenInvalidGrantError,
116 SendAccessTokenInvalidRequestError,
117 },
118 },
119 };
120
121 fn make_send_client(mock_server: &MockServer) -> SendAccessClient {
122 let settings = ClientSettings {
123 identity_url: format!("http://{}/identity", mock_server.address()),
124 api_url: format!("http://{}/api", mock_server.address()),
125 user_agent: "Bitwarden Rust-SDK [TEST]".into(),
126 device_type: DeviceType::SDK,
127 };
128 let core_client = CoreClient::new(Some(settings));
129 core_client.auth_new().send_access()
130 }
131
132 mod request_send_access_token_success_tests {
133
134 use super::*;
135
136 #[tokio::test]
137 async fn request_send_access_token_anon_send_success() {
138 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
139 let scope_str = scope_value.as_str().unwrap();
140
141 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
142 let grant_type_str = grant_type_value.as_str().unwrap();
143
144 let raw_success = serde_json::json!({
146 "access_token": "token",
147 "token_type": "bearer",
148 "expires_in": 3600,
149 "scope": scope_str
150 });
151
152 let req = SendAccessTokenRequest {
154 send_id: "test_send_id".into(),
155 send_access_credentials: None, };
157
158 let mock = Mock::given(matchers::method("POST"))
159 .and(matchers::path("identity/connect/token"))
160 .and(matchers::header(
162 reqwest::header::CONTENT_TYPE.as_str(),
163 "application/x-www-form-urlencoded",
164 ))
165 .and(matchers::header(
166 reqwest::header::ACCEPT.as_str(),
167 "application/json",
168 ))
169 .and(matchers::header(
170 reqwest::header::CACHE_CONTROL.as_str(),
171 "no-store",
172 ))
173 .and(body_string_contains("client_id=send"))
175 .and(body_string_contains(format!(
176 "grant_type={}",
177 grant_type_str
178 )))
179 .and(body_string_contains(format!("scope={}", scope_str)))
180 .and(body_string_contains(format!("send_id={}", req.send_id)))
181 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
183
184 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
186
187 let send_access_client = make_send_client(&mock_server);
189
190 let token: SendAccessTokenResponse = send_access_client
191 .request_send_access_token(req)
192 .await
193 .unwrap();
194
195 assert_eq!(token.token, "token");
196 assert!(token.expires_at > 0);
197 }
198
199 #[tokio::test]
200 async fn request_send_access_token_password_protected_send_success() {
201 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
202 let scope_str = scope_value.as_str().unwrap();
203
204 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
205 let grant_type_str = grant_type_value.as_str().unwrap();
206
207 let raw_success = serde_json::json!({
209 "access_token": "token",
210 "token_type": "bearer",
211 "expires_in": 3600,
212 "scope": scope_str
213 });
214
215 let password_hash_b64 = "valid-hash";
216
217 let password_credentials = SendPasswordCredentials {
218 password_hash_b64: password_hash_b64.into(),
219 };
220
221 let req = SendAccessTokenRequest {
222 send_id: "valid-send-id".into(),
223 send_access_credentials: Some(SendAccessCredentials::Password(
224 password_credentials,
225 )),
226 };
227
228 let mock = Mock::given(matchers::method("POST"))
229 .and(matchers::path("identity/connect/token"))
230 .and(matchers::header(
232 reqwest::header::CONTENT_TYPE.as_str(),
233 "application/x-www-form-urlencoded",
234 ))
235 .and(matchers::header(
236 reqwest::header::ACCEPT.as_str(),
237 "application/json",
238 ))
239 .and(matchers::header(
240 reqwest::header::CACHE_CONTROL.as_str(),
241 "no-store",
242 ))
243 .and(body_string_contains("client_id=send"))
245 .and(body_string_contains(format!(
246 "grant_type={}",
247 grant_type_str
248 )))
249 .and(body_string_contains(format!("scope={}", scope_str)))
250 .and(body_string_contains(format!("send_id={}", req.send_id)))
251 .and(body_string_contains(format!(
252 "password_hash_b64={}",
253 password_hash_b64
254 )))
255 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
257
258 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
260
261 let send_access_client = make_send_client(&mock_server);
263
264 let token: SendAccessTokenResponse = send_access_client
265 .request_send_access_token(req)
266 .await
267 .unwrap();
268
269 assert_eq!(token.token, "token");
270 assert!(token.expires_at > 0);
271 }
272
273 #[tokio::test]
274 async fn request_send_access_token_email_otp_protected_send_success() {
275 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
276 let scope_str = scope_value.as_str().unwrap();
277
278 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
279 let grant_type_str = grant_type_value.as_str().unwrap();
280
281 let raw_success = serde_json::json!({
283 "access_token": "token",
284 "token_type": "bearer",
285 "expires_in": 3600,
286 "scope": scope_str
287 });
288
289 let email = "[email protected]";
290 let otp: &str = "valid_otp";
291
292 let email_otp_credentials = SendEmailOtpCredentials {
293 email: email.into(),
294 otp: otp.into(),
295 };
296
297 let req = SendAccessTokenRequest {
298 send_id: "valid-send-id".into(),
299 send_access_credentials: Some(SendAccessCredentials::EmailOtp(
300 email_otp_credentials,
301 )),
302 };
303
304 let mock = Mock::given(matchers::method("POST"))
305 .and(matchers::path("identity/connect/token"))
306 .and(matchers::header(
308 reqwest::header::CONTENT_TYPE.as_str(),
309 "application/x-www-form-urlencoded",
310 ))
311 .and(matchers::header(
312 reqwest::header::ACCEPT.as_str(),
313 "application/json",
314 ))
315 .and(matchers::header(
316 reqwest::header::CACHE_CONTROL.as_str(),
317 "no-store",
318 ))
319 .and(body_string_contains("client_id=send"))
321 .and(body_string_contains(format!(
322 "grant_type={}",
323 grant_type_str
324 )))
325 .and(body_string_contains(format!("scope={}", scope_str)))
326 .and(body_string_contains(format!("send_id={}", req.send_id)))
327 .and(body_string_contains("email=valid%40email.com"))
328 .and(body_string_contains(format!("otp={}", otp)))
329 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
331
332 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
334
335 let send_access_client = make_send_client(&mock_server);
337
338 let token: SendAccessTokenResponse = send_access_client
339 .request_send_access_token(req)
340 .await
341 .unwrap();
342
343 assert_eq!(token.token, "token");
344 assert!(token.expires_at > 0);
345 }
346 }
347
348 mod request_send_access_token_invalid_request_tests {
349 use super::*;
350
351 #[tokio::test]
352 async fn request_send_access_token_invalid_request_send_id_required_error() {
353 let error_description = "send_id is required.".into();
355 let raw_error = serde_json::json!({
356 "error": "invalid_request",
357 "error_description": error_description,
358 "send_access_error_type": "send_id_required"
359 });
360
361 let mock = Mock::given(matchers::method("POST"))
363 .and(matchers::path("identity/connect/token"))
364 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
365
366 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
368
369 let send_access_client = make_send_client(&mock_server);
371
372 let req = SendAccessTokenRequest {
374 send_id: "".into(),
375 send_access_credentials: None, };
377
378 let result = send_access_client.request_send_access_token(req).await;
379
380 assert!(result.is_err());
381
382 let err = result.unwrap_err();
383 match err {
384 SendAccessTokenError::Expected(api_err) => {
385 assert_eq!(
386 api_err,
387 SendAccessTokenApiErrorResponse::InvalidRequest {
388 send_access_error_type: Some(
389 SendAccessTokenInvalidRequestError::SendIdRequired
390 ),
391 error_description: Some(error_description),
392 }
393 );
394 }
395 other => panic!("expected Response variant, got {:?}", other),
396 }
397 }
398
399 #[tokio::test]
400 async fn request_send_access_token_invalid_request_password_hash_required_error() {
401 let error_description = "password_hash_b64 is required.".into();
403 let raw_error = serde_json::json!({
404 "error": "invalid_request",
405 "error_description": error_description,
406 "send_access_error_type": "password_hash_b64_required"
407 });
408
409 let mock = Mock::given(matchers::method("POST"))
411 .and(matchers::path("identity/connect/token"))
412 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
413
414 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
416
417 let send_access_client = make_send_client(&mock_server);
419
420 let req = SendAccessTokenRequest {
422 send_id: "test_send_id".into(),
423 send_access_credentials: None, };
425
426 let result = send_access_client.request_send_access_token(req).await;
427
428 assert!(result.is_err());
429
430 let err = result.unwrap_err();
431 match err {
432 SendAccessTokenError::Expected(api_err) => {
433 assert_eq!(
434 api_err,
435 SendAccessTokenApiErrorResponse::InvalidRequest {
436 send_access_error_type: Some(
437 SendAccessTokenInvalidRequestError::PasswordHashB64Required
438 ),
439 error_description: Some(error_description),
440 }
441 );
442 }
443 other => panic!("expected Response variant, got {:?}", other),
444 }
445 }
446
447 #[tokio::test]
448 async fn request_send_access_token_invalid_request_email_required_error() {
449 let error_description = "email is required.".into();
451 let raw_error = serde_json::json!({
452 "error": "invalid_request",
453 "error_description": error_description,
454 "send_access_error_type": "email_required"
455 });
456
457 let mock = Mock::given(matchers::method("POST"))
459 .and(matchers::path("identity/connect/token"))
460 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
461
462 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
464
465 let send_access_client = make_send_client(&mock_server);
467
468 let req = SendAccessTokenRequest {
470 send_id: "test_send_id".into(),
471 send_access_credentials: None, };
473
474 let result = send_access_client.request_send_access_token(req).await;
475
476 assert!(result.is_err());
477
478 let err = result.unwrap_err();
479 match err {
480 SendAccessTokenError::Expected(api_err) => {
481 assert_eq!(
482 api_err,
483 SendAccessTokenApiErrorResponse::InvalidRequest {
484 send_access_error_type: Some(
485 SendAccessTokenInvalidRequestError::EmailRequired
486 ),
487 error_description: Some(error_description),
488 }
489 );
490 }
491 other => panic!("expected Response variant, got {:?}", other),
492 }
493 }
494
495 #[tokio::test]
496 async fn request_send_access_token_invalid_request_email_otp_required_error() {
497 let error_description =
499 "email and otp are required. An OTP has been sent to the email address provided."
500 .into();
501 let raw_error = serde_json::json!({
502 "error": "invalid_request",
503 "error_description": error_description,
504 "send_access_error_type": "email_and_otp_required_otp_sent"
505 });
506
507 let mock = Mock::given(matchers::method("POST"))
509 .and(matchers::path("identity/connect/token"))
510 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
511
512 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
514
515 let send_access_client = make_send_client(&mock_server);
517
518 let email_credentials = SendEmailCredentials {
520 email: "[email protected]".into(),
521 };
522
523 let req = SendAccessTokenRequest {
524 send_id: "test_send_id".into(),
525 send_access_credentials: Some(SendAccessCredentials::Email(email_credentials)),
526 };
527
528 let result = send_access_client.request_send_access_token(req).await;
529
530 assert!(result.is_err());
531
532 let err = result.unwrap_err();
533 match err {
534 SendAccessTokenError::Expected(api_err) => {
535 assert_eq!(
536 api_err,
537 SendAccessTokenApiErrorResponse::InvalidRequest {
538 send_access_error_type: Some(
539 SendAccessTokenInvalidRequestError::EmailAndOtpRequiredOtpSent
540 ),
541 error_description: Some(error_description),
542 }
543 );
544 }
545 other => panic!("expected Response variant, got {:?}", other),
546 }
547 }
548 }
549
550 mod request_send_access_token_invalid_grant_tests {
551
552 use super::*;
553
554 #[tokio::test]
555 async fn request_send_access_token_invalid_grant_invalid_send_id_error() {
556 let error_description = "send_id is invalid.".into();
558 let raw_error = serde_json::json!({
559 "error": "invalid_grant",
560 "error_description": error_description,
561 "send_access_error_type": "send_id_invalid"
562 });
563
564 let mock = Mock::given(matchers::method("POST"))
566 .and(matchers::path("identity/connect/token"))
567 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
568
569 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
571
572 let send_access_client = make_send_client(&mock_server);
574
575 let req = SendAccessTokenRequest {
577 send_id: "invalid-send-id".into(),
578 send_access_credentials: None, };
580
581 let result = send_access_client.request_send_access_token(req).await;
582
583 assert!(result.is_err());
584
585 let err = result.unwrap_err();
586 match err {
587 SendAccessTokenError::Expected(api_err) => {
588 assert_eq!(
590 api_err,
591 SendAccessTokenApiErrorResponse::InvalidGrant {
592 send_access_error_type: Some(
593 SendAccessTokenInvalidGrantError::SendIdInvalid
594 ),
595 error_description: Some(error_description),
596 }
597 );
598 }
599 other => panic!("expected Response variant, got {:?}", other),
600 }
601 }
602
603 #[tokio::test]
604 async fn request_send_access_token_invalid_grant_invalid_password_hash_error() {
605 let error_description = "password_hash_b64 is invalid.".into();
607 let raw_error = serde_json::json!({
608 "error": "invalid_grant",
609 "error_description": error_description,
610 "send_access_error_type": "password_hash_b64_invalid"
611 });
612
613 let mock = Mock::given(matchers::method("POST"))
615 .and(matchers::path("identity/connect/token"))
616 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
617
618 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
620
621 let send_access_client = make_send_client(&mock_server);
623
624 let password_credentials = SendPasswordCredentials {
626 password_hash_b64: "invalid-hash".into(),
627 };
628
629 let req = SendAccessTokenRequest {
630 send_id: "valid-send-id".into(),
631 send_access_credentials: Some(SendAccessCredentials::Password(
632 password_credentials,
633 )),
634 };
635
636 let result = send_access_client.request_send_access_token(req).await;
637
638 assert!(result.is_err());
639
640 let err = result.unwrap_err();
641 match err {
642 SendAccessTokenError::Expected(api_err) => {
643 assert_eq!(
645 api_err,
646 SendAccessTokenApiErrorResponse::InvalidGrant {
647 send_access_error_type: Some(
648 SendAccessTokenInvalidGrantError::PasswordHashB64Invalid
649 ),
650 error_description: Some(error_description),
651 }
652 );
653 }
654 other => panic!("expected Response variant, got {:?}", other),
655 }
656 }
657
658 #[tokio::test]
659 async fn request_send_access_token_invalid_grant_invalid_email_error() {
660 let error_description = "email is invalid.".into();
662 let raw_error = serde_json::json!({
663 "error": "invalid_grant",
664 "error_description": error_description,
665 "send_access_error_type": "email_invalid"
666 });
667
668 let mock = Mock::given(matchers::method("POST"))
670 .and(matchers::path("identity/connect/token"))
671 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
672
673 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
675
676 let send_access_client = make_send_client(&mock_server);
678
679 let email_credentials = SendEmailCredentials {
681 email: "invalid-email".into(),
682 };
683 let req = SendAccessTokenRequest {
684 send_id: "valid-send-id".into(),
685 send_access_credentials: Some(SendAccessCredentials::Email(email_credentials)),
686 };
687
688 let result = send_access_client.request_send_access_token(req).await;
689
690 assert!(result.is_err());
691
692 let err = result.unwrap_err();
693 match err {
694 SendAccessTokenError::Expected(api_err) => {
695 assert_eq!(
697 api_err,
698 SendAccessTokenApiErrorResponse::InvalidGrant {
699 send_access_error_type: Some(
700 SendAccessTokenInvalidGrantError::EmailInvalid
701 ),
702 error_description: Some(error_description),
703 }
704 );
705 }
706 other => panic!("expected Response variant, got {:?}", other),
707 }
708 }
709
710 #[tokio::test]
711 async fn request_send_access_token_invalid_grant_invalid_otp_error() {
712 let error_description = "otp is invalid.".into();
714 let raw_error = serde_json::json!({
715 "error": "invalid_grant",
716 "error_description": error_description,
717 "send_access_error_type": "otp_invalid"
718 });
719
720 let mock = Mock::given(matchers::method("POST"))
722 .and(matchers::path("identity/connect/token"))
723 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
724
725 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
727
728 let send_access_client = make_send_client(&mock_server);
730
731 let email_otp_credentials = SendEmailOtpCredentials {
733 email: "[email protected]".into(),
734 otp: "valid_otp".into(),
735 };
736 let req = SendAccessTokenRequest {
737 send_id: "valid-send-id".into(),
738 send_access_credentials: Some(SendAccessCredentials::EmailOtp(
739 email_otp_credentials,
740 )),
741 };
742
743 let result = send_access_client.request_send_access_token(req).await;
744
745 assert!(result.is_err());
746
747 let err = result.unwrap_err();
748 match err {
749 SendAccessTokenError::Expected(api_err) => {
750 assert_eq!(
752 api_err,
753 SendAccessTokenApiErrorResponse::InvalidGrant {
754 send_access_error_type: Some(
755 SendAccessTokenInvalidGrantError::OtpInvalid
756 ),
757 error_description: Some(error_description),
758 }
759 );
760 }
761 other => panic!("expected Response variant, got {:?}", other),
762 }
763 }
764 }
765
766 mod request_send_access_token_unexpected_error_tests {
767
768 use super::*;
769
770 async fn run_case(status_code: u16, reason: &str) {
771 let mock = Mock::given(matchers::method("POST"))
772 .and(matchers::path("identity/connect/token"))
773 .respond_with(ResponseTemplate::new(status_code));
774
775 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
776 let send_access_client = make_send_client(&mock_server);
777
778 let req = SendAccessTokenRequest {
779 send_id: "test_send_id".into(),
780 send_access_credentials: None,
781 };
782
783 let result = send_access_client.request_send_access_token(req).await;
784
785 assert!(result.is_err());
786
787 let err = result.expect_err(&format!(
788 "expected Err for status {} {} against http://{}/identity/connect/token",
789 status_code,
790 reason,
791 mock_server.address()
792 ));
793
794 match err {
795 SendAccessTokenError::Unexpected(api_err) => {
796 let expected = UnexpectedIdentityError(format!(
797 "Received response status {} {} against http://{}/identity/connect/token",
798 status_code,
799 reason,
800 mock_server.address()
801 ));
802 assert_eq!(api_err, expected, "mismatch for status {}", status_code);
803 }
804 other => panic!("expected Unexpected variant, got {:?}", other),
805 }
806 }
807
808 #[tokio::test]
809 async fn request_send_access_token_unexpected_statuses() {
810 let cases = [
811 (401, "Unauthorized"),
814 (402, "Payment Required"),
815 (403, "Forbidden"),
816 (404, "Not Found"),
817 (405, "Method Not Allowed"),
818 (406, "Not Acceptable"),
819 (407, "Proxy Authentication Required"),
820 (408, "Request Timeout"),
821 (409, "Conflict"),
822 (410, "Gone"),
823 (411, "Length Required"),
824 (412, "Precondition Failed"),
825 (413, "Payload Too Large"),
826 (414, "URI Too Long"),
827 (415, "Unsupported Media Type"),
828 (416, "Range Not Satisfiable"),
829 (417, "Expectation Failed"),
830 (421, "Misdirected Request"),
831 (422, "Unprocessable Entity"),
832 (423, "Locked"),
833 (424, "Failed Dependency"),
834 (425, "Too Early"),
835 (426, "Upgrade Required"),
836 (428, "Precondition Required"),
837 (429, "Too Many Requests"),
838 (431, "Request Header Fields Too Large"),
839 (451, "Unavailable For Legal Reasons"),
840 (500, "Internal Server Error"),
842 (501, "Not Implemented"),
843 (502, "Bad Gateway"),
844 (503, "Service Unavailable"),
845 (504, "Gateway Timeout"),
846 (505, "HTTP Version Not Supported"),
847 (506, "Variant Also Negotiates"),
848 (507, "Insufficient Storage"),
849 (508, "Loop Detected"),
850 (510, "Not Extended"),
851 (511, "Network Authentication Required"),
852 ];
853
854 for (code, reason) in cases {
855 run_case(code, reason).await;
856 }
857 }
858 }
859}