sfr_server/handler/
oauth.rs

1//! The trait representing the interface that treats about OAuth.
2
3use sfr_core as sc;
4use sfr_slack_api as ssa;
5use sfr_types as st;
6
7use crate::{OauthRedirectResponse, ResponseError};
8use sc::{OauthRedirectQuery, OauthV2AccessResponse, Slack};
9use ssa::OauthClient;
10use st::OauthV2AccessRequest;
11
12/// The trait representing the interface that treats about OAuth.
13pub trait OauthHandlerTrait: Send + Sync {
14    /// Handles [`OauthV2AccessResponse`].
15    ///
16    /// If this handler returns `None`, the framework will return default value.
17    fn handle_oauth(
18        &self,
19        client: reqwest::Client,
20        body: OauthV2AccessResponse,
21    ) -> impl std::future::Future<Output = Result<Option<OauthRedirectResponse>, ResponseError>> + Send;
22
23    /// Returns OAuth access_token from team_id (An ID of Workspace).
24    fn take_oauth_token_from_team_id(
25        &self,
26        team_id: &str,
27    ) -> impl std::future::Future<Output = Result<String, ResponseError>> + Send;
28
29    /// Returns `grant_type` to create [`OauthV2AccessRequest`] by [`OauthHandlerTrait::oauth_v2_access_request_form`].
30    fn grant_type(&self) -> &str {
31        "authorization_code"
32    }
33    /// Returns `redirect_uri` to create [`OauthV2AccessRequest`] by [`OauthHandlerTrait::oauth_v2_access_request_form`].
34    fn redirect_uri(&self) -> &str;
35
36    /// Creates and returns [`OauthV2AccessRequest`].
37    fn oauth_v2_access_request_form<'a>(
38        &'a self,
39        slack: &'a Slack,
40        code: &'a str,
41    ) -> OauthV2AccessRequest<'a> {
42        OauthV2AccessRequest {
43            client_id: slack.client_id(),
44            client_secret: slack.client_secret(),
45            grant_type: self.grant_type(),
46            redirect_uri: self.redirect_uri(),
47            code,
48        }
49    }
50
51    /// Treats redirect request.
52    fn treat_oauth_redirect<'a>(
53        &'a self,
54        client: reqwest::Client,
55        slack: &'a Slack,
56        code: &'a str,
57    ) -> impl std::future::Future<Output = Result<OauthV2AccessResponse, ResponseError>> + Send + 'a
58    {
59        let client = OauthClient::new(client);
60        let form = self.oauth_v2_access_request_form(slack, code);
61        async move {
62            let resp = client
63                .oauth_v2_access(form)
64                .await
65                .map_err(|e| ResponseError::InternalServerError(Box::new(e)))?;
66            Ok(resp)
67        }
68    }
69}
70
71/// The new type to wrap [`OauthHandlerTrait`].
72#[derive(Clone)]
73pub(crate) struct OauthHandler<T>(T)
74where
75    T: OauthHandlerTrait;
76
77impl<T> OauthHandler<T>
78where
79    T: OauthHandlerTrait,
80{
81    /// The Constructor.
82    pub fn new(inner: T) -> Self {
83        Self(inner)
84    }
85
86    /// The wrapper function for a OAuth.
87    pub fn handle<'a>(
88        &'a self,
89        client: reqwest::Client,
90        slack: Slack,
91        query: OauthRedirectQuery,
92    ) -> impl std::future::Future<Output = Result<impl axum::response::IntoResponse, ResponseError>>
93           + Send
94           + 'a {
95        tracing::info!("oauth redirect: start to process");
96
97        async move {
98            let body = self
99                .0
100                .treat_oauth_redirect(client.clone(), &slack, query.code())
101                .await?;
102
103            let resp = self
104                .0
105                .handle_oauth(client, body)
106                .await?
107                .unwrap_or_else(default_redirect_response);
108
109            Ok(resp)
110        }
111    }
112}
113
114/// The default OAuth redirect response.
115fn default_redirect_response() -> OauthRedirectResponse {
116    #[allow(clippy::missing_docs_in_private_items)] // https://github.com/rust-lang/rust-clippy/issues/13298
117    const DEFAULT_RESPONSE: &str = r#"<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title></title></head><body>ok</body></html>"#;
118
119    OauthRedirectResponse::html(DEFAULT_RESPONSE.into())
120}
121
122impl<T> std::ops::Deref for OauthHandler<T>
123where
124    T: OauthHandlerTrait,
125{
126    type Target = T;
127
128    fn deref(&self) -> &Self::Target {
129        &self.0
130    }
131}