Skip to main content

spatio_sdk/apis/
o_auth_api.rs

1/*
2 * SpatioAPI
3 *
4 * The REST API that owns every resource in your Spatio workspace: notes, sheets, slides, tasks, calendar events, mail, chat, files, and contacts. SpatioMCP wraps this API; Spatio Desktop reads from it. You can call it directly from your own code.  All requests must be authenticated with a Personal Access Token (`Authorization: Bearer pat_...`) or an OAuth 2.1 access token, and use HTTPS.  Official SDKs (MIT, generated from this spec on every release):  - TypeScript: https://github.com/spatio-labs/spatio-ts (`npm install @spatio-labs/spatio-ts`) - Python: https://github.com/spatio-labs/spatio-py (`pip install spatio-sdk`) - Go: https://github.com/spatio-labs/spatio-go (`go get github.com/spatio-labs/spatio-go`)  This specification is generated from the platform-service Go source on every push to `main`. The spec, not hand-written documentation, is the source of truth: server stubs and SDKs are generated from it, and any drift between the spec and the running service fails CI. 
5 *
6 * The version of the OpenAPI document: v1
7 * Contact: hello@spatio.app
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use reqwest;
13use serde::{Deserialize, Serialize, de::Error as _};
14use crate::{apis::ResponseContent, models};
15use super::{Error, configuration, ContentType};
16
17
18/// struct for typed errors of method [`get_jwks`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum GetJwksError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`get_o_auth_discovery`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GetOAuthDiscoveryError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`get_open_id_configuration`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum GetOpenIdConfigurationError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`get_user_info`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum GetUserInfoError {
43    Status401(),
44    UnknownValue(serde_json::Value),
45}
46
47/// struct for typed errors of method [`oauth_authorize`]
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum OauthAuthorizeError {
51    UnknownValue(serde_json::Value),
52}
53
54/// struct for typed errors of method [`oauth_introspect`]
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(untagged)]
57pub enum OauthIntrospectError {
58    UnknownValue(serde_json::Value),
59}
60
61/// struct for typed errors of method [`oauth_revoke`]
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(untagged)]
64pub enum OauthRevokeError {
65    UnknownValue(serde_json::Value),
66}
67
68/// struct for typed errors of method [`oauth_token`]
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(untagged)]
71pub enum OauthTokenError {
72    Status400(models::OAuthError),
73    UnknownValue(serde_json::Value),
74}
75
76/// struct for typed errors of method [`post_user_info`]
77#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(untagged)]
79pub enum PostUserInfoError {
80    UnknownValue(serde_json::Value),
81}
82
83/// struct for typed errors of method [`register_o_auth_client`]
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(untagged)]
86pub enum RegisterOAuthClientError {
87    Status400(models::OAuthError),
88    Status429(),
89    UnknownValue(serde_json::Value),
90}
91
92
93/// The set of public keys RPs use to verify Spatio-issued id_tokens. Cached for 5 minutes at the edge. Always includes the currently-active signing key plus any retired keys that may still be in circulation (id_token TTL is 1 hour + slack). 
94pub async fn get_jwks(configuration: &configuration::Configuration, ) -> Result<models::Jwks, Error<GetJwksError>> {
95
96    let uri_str = format!("{}/.well-known/jwks.json", configuration.base_path);
97    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
98
99    if let Some(ref user_agent) = configuration.user_agent {
100        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
101    }
102
103    let req = req_builder.build()?;
104    let resp = configuration.client.execute(req).await?;
105
106    let status = resp.status();
107    let content_type = resp
108        .headers()
109        .get("content-type")
110        .and_then(|v| v.to_str().ok())
111        .unwrap_or("application/octet-stream");
112    let content_type = super::ContentType::from(content_type);
113
114    if !status.is_client_error() && !status.is_server_error() {
115        let content = resp.text().await?;
116        match content_type {
117            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
118            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::Jwks`"))),
119            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::Jwks`")))),
120        }
121    } else {
122        let content = resp.text().await?;
123        let entity: Option<GetJwksError> = serde_json::from_str(&content).ok();
124        Err(Error::ResponseError(ResponseContent { status, content, entity }))
125    }
126}
127
128/// Returns the canonical metadata for the Spatio OAuth 2.1 + OpenID Connect server. Third-party RPs use this to auto-discover endpoint URLs, supported scopes, and signing algorithms.  Identical payload to `/.well-known/openid-configuration` — either path is acceptable; OIDC clients prefer the openid-configuration alias. 
129pub async fn get_o_auth_discovery(configuration: &configuration::Configuration, ) -> Result<models::DiscoveryDocument, Error<GetOAuthDiscoveryError>> {
130
131    let uri_str = format!("{}/.well-known/oauth-authorization-server", configuration.base_path);
132    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
133
134    if let Some(ref user_agent) = configuration.user_agent {
135        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
136    }
137
138    let req = req_builder.build()?;
139    let resp = configuration.client.execute(req).await?;
140
141    let status = resp.status();
142    let content_type = resp
143        .headers()
144        .get("content-type")
145        .and_then(|v| v.to_str().ok())
146        .unwrap_or("application/octet-stream");
147    let content_type = super::ContentType::from(content_type);
148
149    if !status.is_client_error() && !status.is_server_error() {
150        let content = resp.text().await?;
151        match content_type {
152            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
153            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DiscoveryDocument`"))),
154            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DiscoveryDocument`")))),
155        }
156    } else {
157        let content = resp.text().await?;
158        let entity: Option<GetOAuthDiscoveryError> = serde_json::from_str(&content).ok();
159        Err(Error::ResponseError(ResponseContent { status, content, entity }))
160    }
161}
162
163/// Alias of `/.well-known/oauth-authorization-server`. Provided so OIDC client libraries (NextAuth, Auth.js, oidc-client-ts, passport-openidconnect) auto-detect Spatio as an OIDC provider via their `wellKnown` / `discoveryUrl` config field. 
164pub async fn get_open_id_configuration(configuration: &configuration::Configuration, ) -> Result<models::DiscoveryDocument, Error<GetOpenIdConfigurationError>> {
165
166    let uri_str = format!("{}/.well-known/openid-configuration", configuration.base_path);
167    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
168
169    if let Some(ref user_agent) = configuration.user_agent {
170        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
171    }
172
173    let req = req_builder.build()?;
174    let resp = configuration.client.execute(req).await?;
175
176    let status = resp.status();
177    let content_type = resp
178        .headers()
179        .get("content-type")
180        .and_then(|v| v.to_str().ok())
181        .unwrap_or("application/octet-stream");
182    let content_type = super::ContentType::from(content_type);
183
184    if !status.is_client_error() && !status.is_server_error() {
185        let content = resp.text().await?;
186        match content_type {
187            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
188            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::DiscoveryDocument`"))),
189            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::DiscoveryDocument`")))),
190        }
191    } else {
192        let content = resp.text().await?;
193        let entity: Option<GetOpenIdConfigurationError> = serde_json::from_str(&content).ok();
194        Err(Error::ResponseError(ResponseContent { status, content, entity }))
195    }
196}
197
198/// Returns user claims gated by the scopes on the presenting access token. `sub` is always returned; `email`, `name`, etc. require their respective scopes. 
199pub async fn get_user_info(configuration: &configuration::Configuration, ) -> Result<models::UserInfoResponse, Error<GetUserInfoError>> {
200
201    let uri_str = format!("{}/oauth2/userinfo", configuration.base_path);
202    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
203
204    if let Some(ref user_agent) = configuration.user_agent {
205        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
206    }
207    if let Some(ref token) = configuration.bearer_access_token {
208        req_builder = req_builder.bearer_auth(token.to_owned());
209    };
210
211    let req = req_builder.build()?;
212    let resp = configuration.client.execute(req).await?;
213
214    let status = resp.status();
215    let content_type = resp
216        .headers()
217        .get("content-type")
218        .and_then(|v| v.to_str().ok())
219        .unwrap_or("application/octet-stream");
220    let content_type = super::ContentType::from(content_type);
221
222    if !status.is_client_error() && !status.is_server_error() {
223        let content = resp.text().await?;
224        match content_type {
225            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
226            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::UserInfoResponse`"))),
227            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::UserInfoResponse`")))),
228        }
229    } else {
230        let content = resp.text().await?;
231        let entity: Option<GetUserInfoError> = serde_json::from_str(&content).ok();
232        Err(Error::ResponseError(ResponseContent { status, content, entity }))
233    }
234}
235
236/// Browser-redirect endpoint. Validates the client + redirect_uri, packs the request into a signed JWT, and 302s the user's browser to the consent UI. The consent UI then POSTs to `/oauth2/authorize/confirm` with the user's decision.  OIDC additions: `scope=openid+profile+email`, `nonce`, `prompt` (none|login|consent), `max_age`. 
237pub async fn oauth_authorize(configuration: &configuration::Configuration, client_id: &str, redirect_uri: &str, response_type: &str, code_challenge: &str, code_challenge_method: &str, scope: Option<&str>, state: Option<&str>, nonce: Option<&str>, prompt: Option<&str>, max_age: Option<i32>) -> Result<(), Error<OauthAuthorizeError>> {
238    // add a prefix to parameters to efficiently prevent name collisions
239    let p_query_client_id = client_id;
240    let p_query_redirect_uri = redirect_uri;
241    let p_query_response_type = response_type;
242    let p_query_code_challenge = code_challenge;
243    let p_query_code_challenge_method = code_challenge_method;
244    let p_query_scope = scope;
245    let p_query_state = state;
246    let p_query_nonce = nonce;
247    let p_query_prompt = prompt;
248    let p_query_max_age = max_age;
249
250    let uri_str = format!("{}/oauth2/authorize", configuration.base_path);
251    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
252
253    req_builder = req_builder.query(&[("client_id", &p_query_client_id.to_string())]);
254    req_builder = req_builder.query(&[("redirect_uri", &p_query_redirect_uri.to_string())]);
255    req_builder = req_builder.query(&[("response_type", &p_query_response_type.to_string())]);
256    if let Some(ref param_value) = p_query_scope {
257        req_builder = req_builder.query(&[("scope", &param_value.to_string())]);
258    }
259    if let Some(ref param_value) = p_query_state {
260        req_builder = req_builder.query(&[("state", &param_value.to_string())]);
261    }
262    req_builder = req_builder.query(&[("code_challenge", &p_query_code_challenge.to_string())]);
263    req_builder = req_builder.query(&[("code_challenge_method", &p_query_code_challenge_method.to_string())]);
264    if let Some(ref param_value) = p_query_nonce {
265        req_builder = req_builder.query(&[("nonce", &param_value.to_string())]);
266    }
267    if let Some(ref param_value) = p_query_prompt {
268        req_builder = req_builder.query(&[("prompt", &param_value.to_string())]);
269    }
270    if let Some(ref param_value) = p_query_max_age {
271        req_builder = req_builder.query(&[("max_age", &param_value.to_string())]);
272    }
273    if let Some(ref user_agent) = configuration.user_agent {
274        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
275    }
276
277    let req = req_builder.build()?;
278    let resp = configuration.client.execute(req).await?;
279
280    let status = resp.status();
281
282    if !status.is_client_error() && !status.is_server_error() {
283        Ok(())
284    } else {
285        let content = resp.text().await?;
286        let entity: Option<OauthAuthorizeError> = serde_json::from_str(&content).ok();
287        Err(Error::ResponseError(ResponseContent { status, content, entity }))
288    }
289}
290
291pub async fn oauth_introspect(configuration: &configuration::Configuration, token: &str) -> Result<models::IntrospectionResponse, Error<OauthIntrospectError>> {
292    // add a prefix to parameters to efficiently prevent name collisions
293    let p_form_token = token;
294
295    let uri_str = format!("{}/oauth2/introspect", configuration.base_path);
296    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
297
298    if let Some(ref user_agent) = configuration.user_agent {
299        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
300    }
301    let mut multipart_form_params = std::collections::HashMap::new();
302    multipart_form_params.insert("token", p_form_token.to_string());
303    req_builder = req_builder.form(&multipart_form_params);
304
305    let req = req_builder.build()?;
306    let resp = configuration.client.execute(req).await?;
307
308    let status = resp.status();
309    let content_type = resp
310        .headers()
311        .get("content-type")
312        .and_then(|v| v.to_str().ok())
313        .unwrap_or("application/octet-stream");
314    let content_type = super::ContentType::from(content_type);
315
316    if !status.is_client_error() && !status.is_server_error() {
317        let content = resp.text().await?;
318        match content_type {
319            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
320            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::IntrospectionResponse`"))),
321            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::IntrospectionResponse`")))),
322        }
323    } else {
324        let content = resp.text().await?;
325        let entity: Option<OauthIntrospectError> = serde_json::from_str(&content).ok();
326        Err(Error::ResponseError(ResponseContent { status, content, entity }))
327    }
328}
329
330pub async fn oauth_revoke(configuration: &configuration::Configuration, token: &str) -> Result<(), Error<OauthRevokeError>> {
331    // add a prefix to parameters to efficiently prevent name collisions
332    let p_form_token = token;
333
334    let uri_str = format!("{}/oauth2/revoke", configuration.base_path);
335    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
336
337    if let Some(ref user_agent) = configuration.user_agent {
338        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
339    }
340    let mut multipart_form_params = std::collections::HashMap::new();
341    multipart_form_params.insert("token", p_form_token.to_string());
342    req_builder = req_builder.form(&multipart_form_params);
343
344    let req = req_builder.build()?;
345    let resp = configuration.client.execute(req).await?;
346
347    let status = resp.status();
348
349    if !status.is_client_error() && !status.is_server_error() {
350        Ok(())
351    } else {
352        let content = resp.text().await?;
353        let entity: Option<OauthRevokeError> = serde_json::from_str(&content).ok();
354        Err(Error::ResponseError(ResponseContent { status, content, entity }))
355    }
356}
357
358pub async fn oauth_token(configuration: &configuration::Configuration, grant_type: &str, code: Option<&str>, code_verifier: Option<&str>, redirect_uri: Option<&str>, refresh_token: Option<&str>, client_id: Option<&str>, client_secret: Option<&str>) -> Result<models::TokenResponse, Error<OauthTokenError>> {
359    // add a prefix to parameters to efficiently prevent name collisions
360    let p_form_grant_type = grant_type;
361    let p_form_code = code;
362    let p_form_code_verifier = code_verifier;
363    let p_form_redirect_uri = redirect_uri;
364    let p_form_refresh_token = refresh_token;
365    let p_form_client_id = client_id;
366    let p_form_client_secret = client_secret;
367
368    let uri_str = format!("{}/oauth2/token", configuration.base_path);
369    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
370
371    if let Some(ref user_agent) = configuration.user_agent {
372        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
373    }
374    let mut multipart_form_params = std::collections::HashMap::new();
375    multipart_form_params.insert("grant_type", p_form_grant_type.to_string());
376    if let Some(param_value) = p_form_code {
377        multipart_form_params.insert("code", param_value.to_string());
378    }
379    if let Some(param_value) = p_form_code_verifier {
380        multipart_form_params.insert("code_verifier", param_value.to_string());
381    }
382    if let Some(param_value) = p_form_redirect_uri {
383        multipart_form_params.insert("redirect_uri", param_value.to_string());
384    }
385    if let Some(param_value) = p_form_refresh_token {
386        multipart_form_params.insert("refresh_token", param_value.to_string());
387    }
388    if let Some(param_value) = p_form_client_id {
389        multipart_form_params.insert("client_id", param_value.to_string());
390    }
391    if let Some(param_value) = p_form_client_secret {
392        multipart_form_params.insert("client_secret", param_value.to_string());
393    }
394    req_builder = req_builder.form(&multipart_form_params);
395
396    let req = req_builder.build()?;
397    let resp = configuration.client.execute(req).await?;
398
399    let status = resp.status();
400    let content_type = resp
401        .headers()
402        .get("content-type")
403        .and_then(|v| v.to_str().ok())
404        .unwrap_or("application/octet-stream");
405    let content_type = super::ContentType::from(content_type);
406
407    if !status.is_client_error() && !status.is_server_error() {
408        let content = resp.text().await?;
409        match content_type {
410            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
411            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::TokenResponse`"))),
412            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::TokenResponse`")))),
413        }
414    } else {
415        let content = resp.text().await?;
416        let entity: Option<OauthTokenError> = serde_json::from_str(&content).ok();
417        Err(Error::ResponseError(ResponseContent { status, content, entity }))
418    }
419}
420
421pub async fn post_user_info(configuration: &configuration::Configuration, ) -> Result<models::UserInfoResponse, Error<PostUserInfoError>> {
422
423    let uri_str = format!("{}/oauth2/userinfo", configuration.base_path);
424    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
425
426    if let Some(ref user_agent) = configuration.user_agent {
427        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
428    }
429    if let Some(ref token) = configuration.bearer_access_token {
430        req_builder = req_builder.bearer_auth(token.to_owned());
431    };
432
433    let req = req_builder.build()?;
434    let resp = configuration.client.execute(req).await?;
435
436    let status = resp.status();
437    let content_type = resp
438        .headers()
439        .get("content-type")
440        .and_then(|v| v.to_str().ok())
441        .unwrap_or("application/octet-stream");
442    let content_type = super::ContentType::from(content_type);
443
444    if !status.is_client_error() && !status.is_server_error() {
445        let content = resp.text().await?;
446        match content_type {
447            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
448            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::UserInfoResponse`"))),
449            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::UserInfoResponse`")))),
450        }
451    } else {
452        let content = resp.text().await?;
453        let entity: Option<PostUserInfoError> = serde_json::from_str(&content).ok();
454        Err(Error::ResponseError(ResponseContent { status, content, entity }))
455    }
456}
457
458/// Returns a fresh `client_id` (and, for confidential clients, `client_secret`) plus a one-time `registration_access_token` the client can use later to update its registration. Public clients (mobile, SPA) MUST use `token_endpoint_auth_method: none` and PKCE.  Rate-limited to 10 registrations per hour per source IP. 
459pub async fn register_o_auth_client(configuration: &configuration::Configuration, client_registration_request: models::ClientRegistrationRequest) -> Result<models::ClientRegistrationResponse, Error<RegisterOAuthClientError>> {
460    // add a prefix to parameters to efficiently prevent name collisions
461    let p_body_client_registration_request = client_registration_request;
462
463    let uri_str = format!("{}/oauth2/register", configuration.base_path);
464    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
465
466    if let Some(ref user_agent) = configuration.user_agent {
467        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
468    }
469    req_builder = req_builder.json(&p_body_client_registration_request);
470
471    let req = req_builder.build()?;
472    let resp = configuration.client.execute(req).await?;
473
474    let status = resp.status();
475    let content_type = resp
476        .headers()
477        .get("content-type")
478        .and_then(|v| v.to_str().ok())
479        .unwrap_or("application/octet-stream");
480    let content_type = super::ContentType::from(content_type);
481
482    if !status.is_client_error() && !status.is_server_error() {
483        let content = resp.text().await?;
484        match content_type {
485            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
486            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ClientRegistrationResponse`"))),
487            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ClientRegistrationResponse`")))),
488        }
489    } else {
490        let content = resp.text().await?;
491        let entity: Option<RegisterOAuthClientError> = serde_json::from_str(&content).ok();
492        Err(Error::ResponseError(ResponseContent { status, content, entity }))
493    }
494}
495