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 axum::async_trait;
9use sc::{OauthRedirectQuery, OauthV2AccessResponse, Slack};
10use ssa::OauthClient;
11use st::OauthV2AccessRequest;
12
13#[async_trait]
15pub trait OauthHandlerTrait: Send + Sync {
16 async fn handle_oauth(
20 &self,
21 client: reqwest::Client,
22 body: OauthV2AccessResponse,
23 ) -> Result<Option<OauthRedirectResponse>, ResponseError>;
24
25 async fn take_oauth_token_from_team_id(&self, team_id: &str) -> Result<String, ResponseError>;
27
28 fn grant_type(&self) -> &str {
30 "authorization_code"
31 }
32 fn redirect_uri(&self) -> &str;
34
35 fn oauth_v2_access_request_form<'a>(
37 &'a self,
38 slack: &'a Slack,
39 code: &'a str,
40 ) -> OauthV2AccessRequest<'a> {
41 OauthV2AccessRequest {
42 client_id: slack.client_id(),
43 client_secret: slack.client_secret(),
44 grant_type: self.grant_type(),
45 redirect_uri: self.redirect_uri(),
46 code,
47 }
48 }
49
50 async fn treat_oauth_redirect(
52 &self,
53 client: reqwest::Client,
54 slack: &Slack,
55 code: &str,
56 ) -> Result<OauthV2AccessResponse, ResponseError> {
57 let client = OauthClient::new(client);
58 let form = self.oauth_v2_access_request_form(slack, code);
59 let resp = client
60 .oauth_v2_access(form)
61 .await
62 .map_err(|e| ResponseError::InternalServerError(Box::new(e)))?;
63 Ok(resp)
64 }
65}
66
67#[derive(Clone)]
69pub(crate) struct OauthHandler<T>(T)
70where
71 T: OauthHandlerTrait;
72
73impl<T> OauthHandler<T>
74where
75 T: OauthHandlerTrait,
76{
77 pub fn new(inner: T) -> Self {
79 Self(inner)
80 }
81
82 pub async fn handle(
84 &self,
85 client: reqwest::Client,
86 slack: Slack,
87 query: OauthRedirectQuery,
88 ) -> Result<impl axum::response::IntoResponse, ResponseError> {
89 tracing::info!("oauth redirect: start to process");
90
91 let body = self
92 .0
93 .treat_oauth_redirect(client.clone(), &slack, query.code())
94 .await?;
95
96 let resp = self
97 .0
98 .handle_oauth(client, body)
99 .await?
100 .unwrap_or_else(default_redirect_response);
101
102 Ok(resp)
103 }
104}
105
106fn default_redirect_response() -> OauthRedirectResponse {
108 #[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>"#;
110
111 OauthRedirectResponse::html(DEFAULT_RESPONSE.into())
112}
113
114impl<T> std::ops::Deref for OauthHandler<T>
115where
116 T: OauthHandlerTrait,
117{
118 type Target = T;
119
120 fn deref(&self) -> &Self::Target {
121 &self.0
122 }
123}