1use tonic::{
4 metadata::{Ascii, MetadataValue},
5 service::Interceptor,
6 {Request, Status, transport::Channel},
7};
8
9use crate::error::AuthError;
10use crate::{
11 anytype::ClientCommandsClient,
12 anytype::rpc::account::local_link::{
13 new_challenge::Request as LocalLinkChallengeRequest,
14 new_challenge::Response as LocalLinkChallengeResponse,
15 solve_challenge::Request as LocalLinkSolveRequest,
16 solve_challenge::Response as LocalLinkSolveResponse,
17 },
18 anytype::rpc::wallet::create_session::{
19 Request as CreateSessionRequest, Response as CreateSessionResponse, request::Auth,
20 },
21 model::account::auth::LocalApiScope,
22};
23
24#[derive(Debug, Clone)]
26pub enum SessionAuth {
27 AppKey(String),
29 AccountKey(String),
31 Mnemonic(String),
33 Token(String),
35}
36
37impl SessionAuth {
38 fn into_request(self) -> CreateSessionRequest {
39 let auth = match self {
40 SessionAuth::AppKey(value) => Auth::AppKey(value),
41 SessionAuth::AccountKey(value) => Auth::AccountKey(value),
42 SessionAuth::Mnemonic(value) => Auth::Mnemonic(value),
43 SessionAuth::Token(value) => Auth::Token(value),
44 };
45 CreateSessionRequest { auth: Some(auth) }
46 }
47}
48
49pub async fn create_session(
51 channel: Channel,
52 auth: SessionAuth,
53) -> Result<CreateSessionResponse, AuthError> {
54 let mut client = ClientCommandsClient::new(channel);
55 let request = auth.into_request();
56 let response: tonic::Response<CreateSessionResponse> =
57 client.wallet_create_session(request).await?;
58 let response = response.into_inner();
59
60 if let Some(error) = response.error.as_ref()
61 && error.code != 0
62 {
63 return Err(AuthError::Api {
64 code: error.code,
65 description: error.description.clone(),
66 });
67 }
68
69 Ok(response)
70}
71
72pub async fn create_session_token(
74 channel: Channel,
75 auth: SessionAuth,
76) -> Result<String, AuthError> {
77 let response = create_session(channel, auth).await?;
78 if response.token.is_empty() {
79 return Err(AuthError::EmptyToken);
80 }
81 Ok(response.token)
82}
83
84pub async fn create_session_token_from_app_key(
86 channel: Channel,
87 app_key: impl AsRef<str>,
88) -> Result<String, AuthError> {
89 create_session_token(channel, SessionAuth::AppKey(app_key.as_ref().to_string())).await
90}
91
92pub async fn create_session_token_from_account_key(
94 channel: Channel,
95 account_key: impl AsRef<str>,
96) -> Result<String, AuthError> {
97 create_session_token(
98 channel,
99 SessionAuth::AccountKey(account_key.as_ref().to_string()),
100 )
101 .await
102}
103
104#[derive(Debug, Clone)]
106pub struct LocalLinkCredentials {
107 pub app_key: String,
108 pub session_token: Option<String>,
109}
110
111pub async fn create_local_link_challenge(
113 channel: Channel,
114 app_name: impl Into<String>,
115 scope: LocalApiScope,
116) -> Result<String, AuthError> {
117 let mut client = ClientCommandsClient::new(channel);
118 let request = LocalLinkChallengeRequest {
119 app_name: app_name.into(),
120 scope: scope as i32,
121 };
122 let response: tonic::Response<LocalLinkChallengeResponse> =
123 client.account_local_link_new_challenge(request).await?;
124 let response = response.into_inner();
125 if let Some(error) = response.error.as_ref()
126 && error.code != 0
127 {
128 return Err(AuthError::Api {
129 code: error.code,
130 description: error.description.clone(),
131 });
132 }
133 Ok(response.challenge_id)
134}
135
136pub async fn solve_local_link_challenge(
138 channel: Channel,
139 challenge_id: impl Into<String>,
140 answer: impl Into<String>,
141) -> Result<LocalLinkCredentials, AuthError> {
142 let mut client = ClientCommandsClient::new(channel);
143 let request = LocalLinkSolveRequest {
144 challenge_id: challenge_id.into(),
145 answer: answer.into(),
146 };
147 let response: tonic::Response<LocalLinkSolveResponse> =
148 client.account_local_link_solve_challenge(request).await?;
149 let response = response.into_inner();
150 if let Some(error) = response.error.as_ref()
151 && error.code != 0
152 {
153 return Err(AuthError::Api {
154 code: error.code,
155 description: error.description.clone(),
156 });
157 }
158 Ok(LocalLinkCredentials {
159 app_key: response.app_key,
160 session_token: if response.session_token.is_empty() {
161 None
162 } else {
163 Some(response.session_token)
164 },
165 })
166}
167
168pub fn with_token<T>(mut request: Request<T>, token: &str) -> Result<Request<T>, AuthError> {
170 let token_value: MetadataValue<Ascii> = token.parse()?;
171 request.metadata_mut().insert("token", token_value);
172 Ok(request)
173}
174
175pub struct TokenInterceptor {
177 token: MetadataValue<Ascii>,
178}
179
180impl TokenInterceptor {
181 pub fn new(token: impl AsRef<str>) -> Result<Self, AuthError> {
182 let token_value: MetadataValue<Ascii> = token.as_ref().parse()?;
183 Ok(Self { token: token_value })
184 }
185}
186
187impl Interceptor for TokenInterceptor {
188 fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
189 request.metadata_mut().insert("token", self.token.clone());
190 Ok(request)
191 }
192}