sfr_server/handler/
oauth.rs1use 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
12pub trait OauthHandlerTrait: Send + Sync {
14 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 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 fn grant_type(&self) -> &str {
31 "authorization_code"
32 }
33 fn redirect_uri(&self) -> &str;
35
36 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 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#[derive(Clone)]
73pub(crate) struct OauthHandler<T>(T)
74where
75 T: OauthHandlerTrait;
76
77impl<T> OauthHandler<T>
78where
79 T: OauthHandlerTrait,
80{
81 pub fn new(inner: T) -> Self {
83 Self(inner)
84 }
85
86 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
114fn default_redirect_response() -> OauthRedirectResponse {
116 #[allow(clippy::missing_docs_in_private_items)] 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}