aetheris_client_wasm/
auth.rs1#![allow(clippy::missing_errors_doc)]
3use crate::auth_proto::{
4 ClientMetadata, LoginRequest, LoginResponse, LogoutRequest, LogoutResponse, OtpLoginRequest,
5 OtpRequest, OtpRequestAck, login_request,
6};
7use tonic::codegen::StdError;
8use tonic_web_wasm_client::Client;
9use tracing::{error, info};
10
11pub struct AuthServiceClient<T> {
15 inner: tonic::client::Grpc<T>,
16}
17
18impl<T> AuthServiceClient<T>
19where
20 T: tonic::client::GrpcService<tonic::body::Body>,
21 T::Error: Into<StdError>,
22 T::ResponseBody: tonic::codegen::Body<Data = tonic::codegen::Bytes> + Send + 'static,
23 <T::ResponseBody as tonic::codegen::Body>::Error: Into<StdError> + Send,
24{
25 pub fn new(inner: T) -> Self {
26 Self {
27 inner: tonic::client::Grpc::new(inner),
28 }
29 }
30
31 pub async fn request_otp(
32 &mut self,
33 request: impl tonic::IntoRequest<OtpRequest>,
34 ) -> Result<tonic::Response<OtpRequestAck>, tonic::Status> {
35 self.inner.ready().await.map_err(|e| {
36 tonic::Status::unknown(format!(
37 "Service was not ready: {}",
38 Into::<StdError>::into(e)
39 ))
40 })?;
41 let codec = tonic_prost::ProstCodec::<OtpRequest, OtpRequestAck>::default();
42 let path = tonic::codegen::http::uri::PathAndQuery::from_static(
43 "/aetheris.auth.v1.AuthService/RequestOtp",
44 );
45 self.inner.unary(request.into_request(), path, codec).await
46 }
47
48 pub async fn login(
49 &mut self,
50 request: impl tonic::IntoRequest<LoginRequest>,
51 ) -> Result<tonic::Response<LoginResponse>, tonic::Status> {
52 self.inner.ready().await.map_err(|e| {
53 tonic::Status::unknown(format!(
54 "Service was not ready: {}",
55 Into::<StdError>::into(e)
56 ))
57 })?;
58 let codec = tonic_prost::ProstCodec::<LoginRequest, LoginResponse>::default();
59 let path = tonic::codegen::http::uri::PathAndQuery::from_static(
60 "/aetheris.auth.v1.AuthService/Login",
61 );
62 self.inner.unary(request.into_request(), path, codec).await
63 }
64 pub async fn logout(
65 &mut self,
66 request: impl tonic::IntoRequest<LogoutRequest>,
67 ) -> Result<tonic::Response<LogoutResponse>, tonic::Status> {
68 self.inner.ready().await.map_err(|e| {
69 tonic::Status::unknown(format!(
70 "Service was not ready: {}",
71 Into::<StdError>::into(e)
72 ))
73 })?;
74 let codec = tonic_prost::ProstCodec::<LogoutRequest, LogoutResponse>::default();
75 let path = tonic::codegen::http::uri::PathAndQuery::from_static(
76 "/aetheris.auth.v1.AuthService/Logout",
77 );
78 self.inner.unary(request.into_request(), path, codec).await
79 }
80}
81
82#[allow(clippy::future_not_send)]
83pub async fn request_otp(base_url: String, email: String) -> Result<String, String> {
84 let redacted = email.split('@').nth(1).map_or("[redacted]", |d| d);
85 info!(email_domain = %redacted, "Attempting gRPC OTP request");
86
87 info!(base_url = %base_url, "Initialising gRPC OTP request");
88 let web_client = Client::new(base_url.clone());
89 let mut client = AuthServiceClient::new(web_client);
90
91 let request = tonic::Request::new(OtpRequest { email });
92
93 match client.request_otp(request).await {
94 Ok(response) => {
95 let inner = response.into_inner();
96 info!(request_id = %inner.request_id, "OTP request successful");
97 Ok(inner.request_id)
98 }
99 Err(e) => {
100 let msg = format!("OTP request failed: {}", e.message());
101 error!(error = %msg, code = ?e.code(), "gRPC request rejected");
102 Err(msg)
103 }
104 }
105}
106
107#[allow(clippy::future_not_send)]
109pub async fn login_with_otp(
110 base_url: String,
111 request_id: String,
112 code: String,
113) -> Result<String, String> {
114 info!("Attempting gRPC OTP login");
115
116 info!(base_url = %base_url, "Initialising gRPC OTP login");
117 let web_client = Client::new(base_url.clone());
118 let mut client = AuthServiceClient::new(web_client);
119
120 let request = tonic::Request::new(LoginRequest {
121 method: Some(login_request::Method::Otp(OtpLoginRequest {
122 request_id,
123 code,
124 })),
125 metadata: Some(ClientMetadata {
126 client_version: env!("CARGO_PKG_VERSION").to_string(),
127 platform: "wasm".to_string(),
128 }),
129 });
130
131 match client.login(request).await {
132 Ok(response) => {
133 let inner = response.into_inner();
134 info!("Login successful!");
135 Ok(inner.session_token)
136 }
137 Err(e) => {
138 let msg = format!("Login failed: {}", e.message());
139 error!(error = %msg, code = ?e.code(), "gRPC login rejected");
140 Err(msg)
141 }
142 }
143}
144
145#[allow(clippy::future_not_send)]
146pub async fn logout(base_url: String, session_token: String) -> Result<(), String> {
147 info!("Attempting gRPC logout");
148
149 let web_client = Client::new(base_url);
150 let mut client = AuthServiceClient::new(web_client);
151
152 let request = tonic::Request::new(LogoutRequest { session_token });
153
154 match client.logout(request).await {
155 Ok(response) => {
156 let inner = response.into_inner();
157 if inner.revoked {
158 info!("Logout successful!");
159 Ok(())
160 } else {
161 let msg = "Logout failed: token not revoked by server".to_string();
162 error!(error = %msg, "gRPC logout did not revoke token");
163 Err(msg)
164 }
165 }
166 Err(e) => {
167 let msg = format!("Logout failed: {}", e.message());
168 error!(error = %msg, code = ?e.code(), "gRPC logout rejected");
169 Err(msg)
170 }
171 }
172}