1use serde::{Deserialize, Serialize};
19use thiserror::Error;
20
21use hessra_config::{HessraConfig, Protocol};
22
23#[derive(Error, Debug)]
25pub enum ApiError {
26 #[error("HTTP client error: {0}")]
27 HttpClient(#[from] reqwest::Error),
28
29 #[error("SSL configuration error: {0}")]
30 SslConfig(String),
31
32 #[error("Invalid response: {0}")]
33 InvalidResponse(String),
34
35 #[error("Token request error: {0}")]
36 TokenRequest(String),
37
38 #[error("Token verification error: {0}")]
39 TokenVerification(String),
40
41 #[error("Service chain error: {0}")]
42 ServiceChain(String),
43
44 #[error("Internal error: {0}")]
45 Internal(String),
46
47 #[error("Signoff failed: {0}")]
48 SignoffFailed(String),
49
50 #[error("Missing signoff configuration for service: {0}")]
51 MissingSignoffConfig(String),
52
53 #[error("Invalid signoff response from {service}: {reason}")]
54 InvalidSignoffResponse { service: String, reason: String },
55
56 #[error("Signoff collection incomplete: {missing_signoffs} signoffs remaining")]
57 IncompleteSignoffs { missing_signoffs: usize },
58}
59
60#[derive(Serialize, Deserialize)]
63pub struct TokenRequest {
64 pub resource: String,
66 pub operation: String,
68 #[serde(skip_serializing_if = "Option::is_none")]
73 pub domain: Option<String>,
74}
75
76#[derive(Serialize, Deserialize)]
78pub struct VerifyTokenRequest {
79 pub token: String,
81 pub subject: String,
83 pub resource: String,
85 pub operation: String,
87}
88
89#[derive(Serialize, Deserialize, Debug, Clone)]
91pub struct SignoffInfo {
92 pub component: String,
93 pub authorization_service: String,
94 pub public_key: String,
95}
96
97#[derive(Serialize, Deserialize, Debug, Clone)]
99pub struct SignTokenRequest {
100 pub token: String,
101 pub resource: String,
102 pub operation: String,
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone)]
107pub struct SignTokenResponse {
108 pub response_msg: String,
109 pub signed_token: Option<String>,
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct TokenResponse {
115 pub response_msg: String,
117 pub token: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub pending_signoffs: Option<Vec<SignoffInfo>>,
122}
123
124#[derive(Serialize, Deserialize)]
126pub struct VerifyTokenResponse {
127 pub response_msg: String,
129}
130
131#[derive(Serialize, Deserialize)]
133pub struct PublicKeyResponse {
134 pub response_msg: String,
135 pub public_key: String,
136}
137
138#[derive(Serialize, Deserialize)]
140pub struct CaCertResponse {
141 pub response_msg: String,
142 pub ca_cert_pem: String,
143}
144
145#[derive(Serialize, Deserialize)]
147pub struct VerifyServiceChainTokenRequest {
148 pub token: String,
149 pub subject: String,
150 pub resource: String,
151 pub component: Option<String>,
152}
153
154#[derive(Serialize, Deserialize)]
156pub struct IdentityTokenRequest {
157 pub identifier: Option<String>,
159}
160
161#[derive(Serialize, Deserialize)]
163pub struct RefreshIdentityTokenRequest {
164 pub current_token: String,
166 pub identifier: Option<String>,
168}
169
170#[derive(Serialize, Deserialize, Debug, Clone)]
172pub struct IdentityTokenResponse {
173 pub response_msg: String,
175 pub token: Option<String>,
177 pub expires_in: Option<u64>,
179 pub identity: Option<String>,
181}
182
183#[derive(Serialize, Deserialize)]
185pub struct MintIdentityTokenRequest {
186 pub subject: String,
188 pub duration: Option<u64>,
190}
191
192#[derive(Serialize, Deserialize, Debug, Clone)]
194pub struct MintIdentityTokenResponse {
195 pub response_msg: String,
197 pub token: Option<String>,
199 pub expires_in: Option<u64>,
201 pub identity: Option<String>,
203}
204
205#[derive(Clone)]
207pub struct BaseConfig {
208 pub base_url: String,
210 pub port: Option<u16>,
212 pub mtls_key: Option<String>,
214 pub mtls_cert: Option<String>,
216 pub server_ca: String,
218 pub public_key: Option<String>,
220 pub personal_keypair: Option<String>,
222}
223
224impl BaseConfig {
225 pub fn get_base_url(&self) -> String {
227 match self.port {
228 Some(port) => format!("{}:{port}", self.base_url),
229 None => self.base_url.clone(),
230 }
231 }
232}
233
234pub struct Http1Client {
236 config: BaseConfig,
238 client: reqwest::Client,
240}
241
242impl Http1Client {
243 pub fn new(config: BaseConfig) -> Result<Self, ApiError> {
245 let certs =
247 reqwest::Certificate::from_pem_bundle(config.server_ca.as_bytes()).map_err(|e| {
248 ApiError::SslConfig(format!("Failed to parse CA certificate chain: {e}"))
249 })?;
250
251 let mut client_builder = reqwest::ClientBuilder::new().use_rustls_tls();
253
254 for cert in certs {
256 client_builder = client_builder.add_root_certificate(cert);
257 }
258
259 if let (Some(cert), Some(key)) = (&config.mtls_cert, &config.mtls_key) {
261 let identity_str = format!("{cert}{key}");
262 let identity = reqwest::Identity::from_pem(identity_str.as_bytes()).map_err(|e| {
263 ApiError::SslConfig(format!(
264 "Failed to create identity from certificate and key: {e}"
265 ))
266 })?;
267 client_builder = client_builder.identity(identity);
268 }
269
270 let client = client_builder
271 .build()
272 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
273
274 Ok(Self { config, client })
275 }
276
277 pub async fn send_request<T, R>(&self, endpoint: &str, request_body: &T) -> Result<R, ApiError>
279 where
280 T: Serialize,
281 R: for<'de> Deserialize<'de>,
282 {
283 let base_url = self.config.get_base_url();
284 let url = format!("https://{base_url}/{endpoint}");
285
286 let response = self
287 .client
288 .post(&url)
289 .json(request_body)
290 .send()
291 .await
292 .map_err(ApiError::HttpClient)?;
293
294 if !response.status().is_success() {
295 let status = response.status();
296 let error_text = response.text().await.unwrap_or_default();
297 return Err(ApiError::InvalidResponse(format!(
298 "HTTP error: {status} - {error_text}"
299 )));
300 }
301
302 let result = response
303 .json::<R>()
304 .await
305 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
306
307 Ok(result)
308 }
309
310 pub async fn send_request_with_auth<T, R>(
311 &self,
312 endpoint: &str,
313 request_body: &T,
314 auth_header: &str,
315 ) -> Result<R, ApiError>
316 where
317 T: Serialize,
318 R: for<'de> Deserialize<'de>,
319 {
320 let base_url = self.config.get_base_url();
321 let url = format!("https://{base_url}/{endpoint}");
322
323 let response = self
324 .client
325 .post(&url)
326 .header("Authorization", auth_header)
327 .json(request_body)
328 .send()
329 .await
330 .map_err(ApiError::HttpClient)?;
331
332 if !response.status().is_success() {
333 let status = response.status();
334 let error_text = response.text().await.unwrap_or_default();
335 return Err(ApiError::InvalidResponse(format!(
336 "HTTP error: {status} - {error_text}"
337 )));
338 }
339
340 let result = response
341 .json::<R>()
342 .await
343 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
344
345 Ok(result)
346 }
347}
348
349#[cfg(feature = "http3")]
351pub struct Http3Client {
352 config: BaseConfig,
354 client: reqwest::Client,
356}
357
358#[cfg(feature = "http3")]
359impl Http3Client {
360 pub fn new(config: BaseConfig) -> Result<Self, ApiError> {
362 let certs =
364 reqwest::Certificate::from_pem_bundle(config.server_ca.as_bytes()).map_err(|e| {
365 ApiError::SslConfig(format!("Failed to parse CA certificate chain: {e}"))
366 })?;
367
368 let mut client_builder = reqwest::ClientBuilder::new()
370 .use_rustls_tls()
371 .http3_prior_knowledge();
372
373 for cert in certs {
375 client_builder = client_builder.add_root_certificate(cert);
376 }
377
378 if let (Some(cert), Some(key)) = (&config.mtls_cert, &config.mtls_key) {
380 let identity_str = format!("{}{}", cert, key);
381 let identity = reqwest::Identity::from_pem(identity_str.as_bytes()).map_err(|e| {
382 ApiError::SslConfig(format!(
383 "Failed to create identity from certificate and key: {e}"
384 ))
385 })?;
386 client_builder = client_builder.identity(identity);
387 }
388
389 let client = client_builder
390 .build()
391 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
392
393 Ok(Self { config, client })
394 }
395
396 pub async fn send_request<T, R>(&self, endpoint: &str, request_body: &T) -> Result<R, ApiError>
398 where
399 T: Serialize,
400 R: for<'de> Deserialize<'de>,
401 {
402 let base_url = self.config.get_base_url();
403 let url = format!("https://{base_url}/{endpoint}");
404
405 let response = self
406 .client
407 .post(&url)
408 .json(request_body)
409 .send()
410 .await
411 .map_err(ApiError::HttpClient)?;
412
413 if !response.status().is_success() {
414 let status = response.status();
415 let error_text = response.text().await.unwrap_or_default();
416 return Err(ApiError::InvalidResponse(format!(
417 "HTTP error: {status} - {error_text}"
418 )));
419 }
420
421 let result = response
422 .json::<R>()
423 .await
424 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
425
426 Ok(result)
427 }
428
429 pub async fn send_request_with_auth<T, R>(
430 &self,
431 endpoint: &str,
432 request_body: &T,
433 auth_header: &str,
434 ) -> Result<R, ApiError>
435 where
436 T: Serialize,
437 R: for<'de> Deserialize<'de>,
438 {
439 let base_url = self.config.get_base_url();
440 let url = format!("https://{base_url}/{endpoint}");
441
442 let response = self
443 .client
444 .post(&url)
445 .header("Authorization", auth_header)
446 .json(request_body)
447 .send()
448 .await
449 .map_err(ApiError::HttpClient)?;
450
451 if !response.status().is_success() {
452 let status = response.status();
453 let error_text = response.text().await.unwrap_or_default();
454 return Err(ApiError::InvalidResponse(format!(
455 "HTTP error: {status} - {error_text}"
456 )));
457 }
458
459 let result = response
460 .json::<R>()
461 .await
462 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
463
464 Ok(result)
465 }
466}
467
468pub enum HessraClient {
470 Http1(Http1Client),
472 #[cfg(feature = "http3")]
474 Http3(Http3Client),
475}
476
477pub struct HessraClientBuilder {
479 config: BaseConfig,
481 protocol: hessra_config::Protocol,
483}
484
485impl HessraClientBuilder {
486 pub fn new() -> Self {
488 Self {
489 config: BaseConfig {
490 base_url: String::new(),
491 port: None,
492 mtls_key: None,
493 mtls_cert: None,
494 server_ca: String::new(),
495 public_key: None,
496 personal_keypair: None,
497 },
498 protocol: Protocol::Http1,
499 }
500 }
501
502 pub fn from_config(mut self, config: &HessraConfig) -> Self {
504 self.config.base_url = config.base_url.clone();
505 self.config.port = config.port;
506 self.config.mtls_key = config.mtls_key.clone();
507 self.config.mtls_cert = config.mtls_cert.clone();
508 self.config.server_ca = config.server_ca.clone();
509 self.config.public_key = config.public_key.clone();
510 self.config.personal_keypair = config.personal_keypair.clone();
511 self.protocol = config.protocol.clone();
512 self
513 }
514
515 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
517 self.config.base_url = base_url.into();
518 self
519 }
520
521 pub fn mtls_key(mut self, mtls_key: impl Into<String>) -> Self {
524 self.config.mtls_key = Some(mtls_key.into());
525 self
526 }
527
528 pub fn mtls_cert(mut self, mtls_cert: impl Into<String>) -> Self {
531 self.config.mtls_cert = Some(mtls_cert.into());
532 self
533 }
534
535 pub fn server_ca(mut self, server_ca: impl Into<String>) -> Self {
538 self.config.server_ca = server_ca.into();
539 self
540 }
541
542 pub fn port(mut self, port: u16) -> Self {
544 self.config.port = Some(port);
545 self
546 }
547
548 pub fn protocol(mut self, protocol: Protocol) -> Self {
550 self.protocol = protocol;
551 self
552 }
553
554 pub fn public_key(mut self, public_key: impl Into<String>) -> Self {
557 self.config.public_key = Some(public_key.into());
558 self
559 }
560
561 pub fn personal_keypair(mut self, keypair: impl Into<String>) -> Self {
565 self.config.personal_keypair = Some(keypair.into());
566 self
567 }
568
569 fn build_http1(&self) -> Result<Http1Client, ApiError> {
571 Http1Client::new(self.config.clone())
572 }
573
574 #[cfg(feature = "http3")]
576 fn build_http3(&self) -> Result<Http3Client, ApiError> {
577 Http3Client::new(self.config.clone())
578 }
579
580 pub fn build(self) -> Result<HessraClient, ApiError> {
582 match self.protocol {
583 Protocol::Http1 => Ok(HessraClient::Http1(self.build_http1()?)),
584 #[cfg(feature = "http3")]
585 Protocol::Http3 => Ok(HessraClient::Http3(self.build_http3()?)),
586 #[allow(unreachable_patterns)]
587 _ => Err(ApiError::Internal("Unsupported protocol".to_string())),
588 }
589 }
590}
591
592impl Default for HessraClientBuilder {
593 fn default() -> Self {
594 Self::new()
595 }
596}
597
598impl HessraClient {
599 pub fn builder() -> HessraClientBuilder {
601 HessraClientBuilder::new()
602 }
603
604 pub async fn fetch_public_key(
608 base_url: impl Into<String>,
609 port: Option<u16>,
610 server_ca: impl Into<String>,
611 ) -> Result<String, ApiError> {
612 let base_url = base_url.into();
613 let server_ca = server_ca.into();
614
615 let cert_pem = server_ca.as_bytes();
617 let certs = reqwest::Certificate::from_pem_bundle(cert_pem)
618 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
619
620 let mut client_builder = reqwest::ClientBuilder::new().use_rustls_tls();
621 for cert in certs {
622 client_builder = client_builder.add_root_certificate(cert);
623 }
624
625 let client = client_builder
626 .build()
627 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
628
629 let url = match port {
631 Some(port) => format!("https://{base_url}:{port}/public_key"),
632 None => format!("https://{base_url}/public_key"),
633 };
634
635 let response = client
637 .get(&url)
638 .send()
639 .await
640 .map_err(ApiError::HttpClient)?;
641
642 if !response.status().is_success() {
643 let status = response.status();
644 let error_text = response.text().await.unwrap_or_default();
645 return Err(ApiError::InvalidResponse(format!(
646 "HTTP error: {status} - {error_text}"
647 )));
648 }
649
650 let result = response
652 .json::<PublicKeyResponse>()
653 .await
654 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
655
656 Ok(result.public_key)
657 }
658
659 pub async fn fetch_ca_cert(
671 base_url: impl Into<String>,
672 port: Option<u16>,
673 ) -> Result<String, ApiError> {
674 let base_url = base_url.into();
675
676 let client = reqwest::ClientBuilder::new()
678 .use_rustls_tls()
679 .build()
680 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
681
682 let url = match port {
684 Some(port) => format!("https://{base_url}:{port}/ca_cert"),
685 None => format!("https://{base_url}/ca_cert"),
686 };
687
688 let response = client
690 .get(&url)
691 .send()
692 .await
693 .map_err(ApiError::HttpClient)?;
694
695 if !response.status().is_success() {
696 let status = response.status();
697 let error_text = response.text().await.unwrap_or_default();
698 return Err(ApiError::InvalidResponse(format!(
699 "HTTP error: {status} - {error_text}"
700 )));
701 }
702
703 let result = response
705 .json::<CaCertResponse>()
706 .await
707 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
708
709 if result.ca_cert_pem.is_empty() {
711 return Err(ApiError::InvalidResponse(
712 "Server returned empty CA certificate".to_string(),
713 ));
714 }
715
716 if !result.ca_cert_pem.contains("-----BEGIN CERTIFICATE-----") {
717 return Err(ApiError::InvalidResponse(
718 "Server returned invalid PEM format".to_string(),
719 ));
720 }
721
722 Ok(result.ca_cert_pem)
723 }
724
725 #[cfg(feature = "http3")]
726 pub async fn fetch_public_key_http3(
727 base_url: impl Into<String>,
728 port: Option<u16>,
729 server_ca: impl Into<String>,
730 ) -> Result<String, ApiError> {
731 let base_url = base_url.into();
732 let server_ca = server_ca.into();
733
734 let cert_pem = server_ca.as_bytes();
736 let certs = reqwest::Certificate::from_pem_bundle(cert_pem)
737 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
738
739 let mut client_builder = reqwest::ClientBuilder::new()
740 .use_rustls_tls()
741 .http3_prior_knowledge();
742 for cert in certs {
743 client_builder = client_builder.add_root_certificate(cert);
744 }
745
746 let client = client_builder
747 .build()
748 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
749
750 let url = match port {
752 Some(port) => format!("https://{base_url}:{port}/public_key"),
753 None => format!("https://{base_url}/public_key"),
754 };
755
756 let response = client
758 .get(&url)
759 .send()
760 .await
761 .map_err(ApiError::HttpClient)?;
762
763 if !response.status().is_success() {
764 let status = response.status();
765 let error_text = response.text().await.unwrap_or_default();
766 return Err(ApiError::InvalidResponse(format!(
767 "HTTP error: {status} - {error_text}"
768 )));
769 }
770
771 let result = response
773 .json::<PublicKeyResponse>()
774 .await
775 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
776
777 Ok(result.public_key)
778 }
779
780 pub async fn request_token(
788 &self,
789 resource: String,
790 operation: String,
791 domain: Option<String>,
792 ) -> Result<TokenResponse, ApiError> {
793 let request = TokenRequest {
794 resource,
795 operation,
796 domain,
797 };
798
799 let response = match self {
800 HessraClient::Http1(client) => {
801 client
802 .send_request::<_, TokenResponse>("request_token", &request)
803 .await?
804 }
805 #[cfg(feature = "http3")]
806 HessraClient::Http3(client) => {
807 client
808 .send_request::<_, TokenResponse>("request_token", &request)
809 .await?
810 }
811 };
812
813 Ok(response)
814 }
815
816 pub async fn request_token_with_identity(
826 &self,
827 resource: String,
828 operation: String,
829 identity_token: String,
830 domain: Option<String>,
831 ) -> Result<TokenResponse, ApiError> {
832 let request = TokenRequest {
833 resource,
834 operation,
835 domain,
836 };
837
838 let response = match self {
839 HessraClient::Http1(client) => {
840 client
841 .send_request_with_auth::<_, TokenResponse>(
842 "request_token",
843 &request,
844 &format!("Bearer {identity_token}"),
845 )
846 .await?
847 }
848 #[cfg(feature = "http3")]
849 HessraClient::Http3(client) => {
850 client
851 .send_request_with_auth::<_, TokenResponse>(
852 "request_token",
853 &request,
854 &format!("Bearer {identity_token}"),
855 )
856 .await?
857 }
858 };
859
860 Ok(response)
861 }
862
863 pub async fn request_token_simple(
866 &self,
867 resource: String,
868 operation: String,
869 ) -> Result<String, ApiError> {
870 let response = self.request_token(resource, operation, None).await?;
871
872 match response.token {
873 Some(token) => Ok(token),
874 None => Err(ApiError::TokenRequest(format!(
875 "Failed to get token: {}",
876 response.response_msg
877 ))),
878 }
879 }
880
881 pub async fn verify_token(
884 &self,
885 token: String,
886 subject: String,
887 resource: String,
888 operation: String,
889 ) -> Result<String, ApiError> {
890 let request = VerifyTokenRequest {
891 token,
892 subject,
893 resource,
894 operation,
895 };
896
897 let response = match self {
898 HessraClient::Http1(client) => {
899 client
900 .send_request::<_, VerifyTokenResponse>("verify_token", &request)
901 .await?
902 }
903 #[cfg(feature = "http3")]
904 HessraClient::Http3(client) => {
905 client
906 .send_request::<_, VerifyTokenResponse>("verify_token", &request)
907 .await?
908 }
909 };
910
911 Ok(response.response_msg)
912 }
913
914 pub async fn verify_service_chain_token(
921 &self,
922 token: String,
923 subject: String,
924 resource: String,
925 component: Option<String>,
926 ) -> Result<String, ApiError> {
927 let request = VerifyServiceChainTokenRequest {
928 token,
929 subject,
930 resource,
931 component,
932 };
933
934 let response = match self {
935 HessraClient::Http1(client) => {
936 client
937 .send_request::<_, VerifyTokenResponse>("verify_service_chain_token", &request)
938 .await?
939 }
940 #[cfg(feature = "http3")]
941 HessraClient::Http3(client) => {
942 client
943 .send_request::<_, VerifyTokenResponse>("verify_service_chain_token", &request)
944 .await?
945 }
946 };
947
948 Ok(response.response_msg)
949 }
950
951 pub async fn sign_token(
953 &self,
954 token: &str,
955 resource: &str,
956 operation: &str,
957 ) -> Result<SignTokenResponse, ApiError> {
958 let request = SignTokenRequest {
959 token: token.to_string(),
960 resource: resource.to_string(),
961 operation: operation.to_string(),
962 };
963
964 let response = match self {
965 HessraClient::Http1(client) => {
966 client
967 .send_request::<_, SignTokenResponse>("sign_token", &request)
968 .await?
969 }
970 #[cfg(feature = "http3")]
971 HessraClient::Http3(client) => {
972 client
973 .send_request::<_, SignTokenResponse>("sign_token", &request)
974 .await?
975 }
976 };
977
978 Ok(response)
979 }
980
981 pub async fn get_public_key(&self) -> Result<String, ApiError> {
983 let url_path = "public_key";
984
985 let response = match self {
986 HessraClient::Http1(client) => {
987 let base_url = client.config.get_base_url();
989 let full_url = format!("https://{base_url}/{url_path}");
990
991 let response = client
992 .client
993 .get(&full_url)
994 .send()
995 .await
996 .map_err(ApiError::HttpClient)?;
997
998 if !response.status().is_success() {
999 let status = response.status();
1000 let error_text = response.text().await.unwrap_or_default();
1001 return Err(ApiError::InvalidResponse(format!(
1002 "HTTP error: {status} - {error_text}"
1003 )));
1004 }
1005
1006 response.json::<PublicKeyResponse>().await.map_err(|e| {
1007 ApiError::InvalidResponse(format!("Failed to parse response: {e}"))
1008 })?
1009 }
1010 #[cfg(feature = "http3")]
1011 HessraClient::Http3(client) => {
1012 let base_url = client.config.get_base_url();
1013 let full_url = format!("https://{base_url}/{url_path}");
1014
1015 let response = client
1016 .client
1017 .get(&full_url)
1018 .send()
1019 .await
1020 .map_err(ApiError::HttpClient)?;
1021
1022 if !response.status().is_success() {
1023 let status = response.status();
1024 let error_text = response.text().await.unwrap_or_default();
1025 return Err(ApiError::InvalidResponse(format!(
1026 "HTTP error: {status} - {error_text}"
1027 )));
1028 }
1029
1030 response.json::<PublicKeyResponse>().await.map_err(|e| {
1031 ApiError::InvalidResponse(format!("Failed to parse response: {e}"))
1032 })?
1033 }
1034 };
1035
1036 Ok(response.public_key)
1037 }
1038
1039 pub async fn request_identity_token(
1047 &self,
1048 identifier: Option<String>,
1049 ) -> Result<IdentityTokenResponse, ApiError> {
1050 let request = IdentityTokenRequest { identifier };
1051
1052 let response = match self {
1053 HessraClient::Http1(client) => {
1054 client
1055 .send_request::<_, IdentityTokenResponse>("request_identity_token", &request)
1056 .await?
1057 }
1058 #[cfg(feature = "http3")]
1059 HessraClient::Http3(client) => {
1060 client
1061 .send_request::<_, IdentityTokenResponse>("request_identity_token", &request)
1062 .await?
1063 }
1064 };
1065
1066 Ok(response)
1067 }
1068
1069 pub async fn refresh_identity_token(
1079 &self,
1080 current_token: String,
1081 identifier: Option<String>,
1082 ) -> Result<IdentityTokenResponse, ApiError> {
1083 let request = RefreshIdentityTokenRequest {
1084 current_token,
1085 identifier,
1086 };
1087
1088 let response = match self {
1089 HessraClient::Http1(client) => {
1090 client
1091 .send_request::<_, IdentityTokenResponse>("refresh_identity_token", &request)
1092 .await?
1093 }
1094 #[cfg(feature = "http3")]
1095 HessraClient::Http3(client) => {
1096 client
1097 .send_request::<_, IdentityTokenResponse>("refresh_identity_token", &request)
1098 .await?
1099 }
1100 };
1101
1102 Ok(response)
1103 }
1104
1105 pub async fn mint_domain_restricted_identity_token(
1115 &self,
1116 subject: String,
1117 duration: Option<u64>,
1118 ) -> Result<MintIdentityTokenResponse, ApiError> {
1119 let request = MintIdentityTokenRequest { subject, duration };
1120
1121 let response = match self {
1122 HessraClient::Http1(client) => {
1123 client
1124 .send_request::<_, MintIdentityTokenResponse>("mint_identity_token", &request)
1125 .await?
1126 }
1127 #[cfg(feature = "http3")]
1128 HessraClient::Http3(client) => {
1129 client
1130 .send_request::<_, MintIdentityTokenResponse>("mint_identity_token", &request)
1131 .await?
1132 }
1133 };
1134
1135 Ok(response)
1136 }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142
1143 #[test]
1145 fn test_base_config_get_base_url_with_port() {
1146 let config = BaseConfig {
1147 base_url: "test.hessra.net".to_string(),
1148 port: Some(443),
1149 mtls_key: None,
1150 mtls_cert: None,
1151 server_ca: "".to_string(),
1152 public_key: None,
1153 personal_keypair: None,
1154 };
1155
1156 assert_eq!(config.get_base_url(), "test.hessra.net:443");
1157 }
1158
1159 #[test]
1160 fn test_base_config_get_base_url_without_port() {
1161 let config = BaseConfig {
1162 base_url: "test.hessra.net".to_string(),
1163 port: None,
1164 mtls_key: None,
1165 mtls_cert: None,
1166 server_ca: "".to_string(),
1167 public_key: None,
1168 personal_keypair: None,
1169 };
1170
1171 assert_eq!(config.get_base_url(), "test.hessra.net");
1172 }
1173
1174 #[test]
1176 fn test_client_builder_methods() {
1177 let builder = HessraClientBuilder::new()
1178 .base_url("test.hessra.net")
1179 .port(443)
1180 .protocol(Protocol::Http1)
1181 .mtls_cert("CERT")
1182 .mtls_key("KEY")
1183 .server_ca("CA")
1184 .public_key("PUBKEY")
1185 .personal_keypair("KEYPAIR");
1186
1187 assert_eq!(builder.config.base_url, "test.hessra.net");
1188 assert_eq!(builder.config.port, Some(443));
1189 assert_eq!(builder.config.mtls_cert, Some("CERT".to_string()));
1190 assert_eq!(builder.config.mtls_key, Some("KEY".to_string()));
1191 assert_eq!(builder.config.server_ca, "CA");
1192 assert_eq!(builder.config.public_key, Some("PUBKEY".to_string()));
1193 assert_eq!(builder.config.personal_keypair, Some("KEYPAIR".to_string()));
1194 }
1195}