slacko/api/
openid.rs

1//! OpenID Connect API
2//!
3//! Methods for OpenID Connect (OIDC) authentication flows.
4//! Use this for implementing Sign in with Slack and identity verification.
5
6use crate::client::SlackClient;
7use crate::error::Result;
8use serde::{Deserialize, Serialize};
9
10/// OpenID Connect API client
11pub struct OpenIDApi {
12    client: SlackClient,
13}
14
15/// Request for openid.connect.token
16#[derive(Debug, Serialize)]
17pub struct OpenIDTokenRequest {
18    /// The authorization code from the OAuth redirect
19    pub code: String,
20    /// Your app's client ID
21    pub client_id: String,
22    /// Your app's client secret
23    pub client_secret: String,
24    /// The redirect URI used in the authorization request
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub redirect_uri: Option<String>,
27    /// Grant type (always "authorization_code" for code exchange)
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub grant_type: Option<String>,
30    /// Refresh token for token refresh flow
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub refresh_token: Option<String>,
33}
34
35/// Response from openid.connect.token
36#[derive(Debug, Deserialize)]
37pub struct OpenIDTokenResponse {
38    pub ok: bool,
39    pub access_token: String,
40    pub token_type: String,
41    pub id_token: String,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub refresh_token: Option<String>,
44    pub expires_in: Option<i64>,
45}
46
47/// Response from openid.connect.userInfo
48#[derive(Debug, Deserialize)]
49pub struct UserInfoResponse {
50    pub ok: bool,
51    pub sub: String, // User ID
52    #[serde(rename = "https://slack.com/user_id")]
53    pub user_id: Option<String>,
54    #[serde(rename = "https://slack.com/team_id")]
55    pub team_id: Option<String>,
56    pub email: Option<String>,
57    pub email_verified: Option<bool>,
58    pub name: Option<String>,
59    pub picture: Option<String>,
60    pub given_name: Option<String>,
61    pub family_name: Option<String>,
62    pub locale: Option<String>,
63    #[serde(rename = "https://slack.com/team_name")]
64    pub team_name: Option<String>,
65    #[serde(rename = "https://slack.com/team_domain")]
66    pub team_domain: Option<String>,
67    #[serde(rename = "https://slack.com/user_image_24")]
68    pub user_image_24: Option<String>,
69    #[serde(rename = "https://slack.com/user_image_32")]
70    pub user_image_32: Option<String>,
71    #[serde(rename = "https://slack.com/user_image_48")]
72    pub user_image_48: Option<String>,
73    #[serde(rename = "https://slack.com/user_image_72")]
74    pub user_image_72: Option<String>,
75    #[serde(rename = "https://slack.com/user_image_192")]
76    pub user_image_192: Option<String>,
77    #[serde(rename = "https://slack.com/user_image_512")]
78    pub user_image_512: Option<String>,
79}
80
81impl OpenIDApi {
82    pub(crate) fn new(client: SlackClient) -> Self {
83        Self { client }
84    }
85
86    /// Exchange an authorization code for an OpenID Connect token
87    ///
88    /// This method exchanges a verification code for an access token and ID token.
89    /// The ID token contains claims about the authenticated user.
90    ///
91    /// # Arguments
92    ///
93    /// * `code` - The authorization code from the OAuth redirect
94    /// * `client_id` - Your app's client ID
95    /// * `client_secret` - Your app's client secret
96    /// * `redirect_uri` - Optional redirect URI (must match authorization request)
97    ///
98    /// # Example
99    ///
100    /// ```no_run
101    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
102    /// # use slacko::{SlackClient, AuthConfig};
103    /// # let client = SlackClient::new(AuthConfig::bot("xoxb-token"))?;
104    /// let openid = client.openid();
105    ///
106    /// // After user authorizes via Sign in with Slack
107    /// let response = openid.token(
108    ///     "authorization_code_here",
109    ///     "your_client_id",
110    ///     "your_client_secret",
111    ///     Some("https://yourapp.com/auth/callback")
112    /// ).await?;
113    ///
114    /// println!("ID token: {}", response.id_token);
115    /// println!("Access token: {}", response.access_token);
116    /// # Ok(())
117    /// # }
118    /// ```
119    pub async fn token(
120        &self,
121        code: &str,
122        client_id: &str,
123        client_secret: &str,
124        redirect_uri: Option<&str>,
125    ) -> Result<OpenIDTokenResponse> {
126        let request = OpenIDTokenRequest {
127            code: code.to_string(),
128            client_id: client_id.to_string(),
129            client_secret: client_secret.to_string(),
130            redirect_uri: redirect_uri.map(|s| s.to_string()),
131            grant_type: Some("authorization_code".to_string()),
132            refresh_token: None,
133        };
134        self.client.post("openid.connect.token", &request).await
135    }
136
137    /// Refresh an OpenID Connect access token
138    ///
139    /// Exchange a refresh token for a new access token and ID token.
140    ///
141    /// # Arguments
142    ///
143    /// * `refresh_token` - The refresh token from a previous token exchange
144    /// * `client_id` - Your app's client ID
145    /// * `client_secret` - Your app's client secret
146    ///
147    /// # Example
148    ///
149    /// ```no_run
150    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
151    /// # use slacko::{SlackClient, AuthConfig};
152    /// # let client = SlackClient::new(AuthConfig::bot("xoxb-token"))?;
153    /// let openid = client.openid();
154    ///
155    /// let response = openid.refresh_token(
156    ///     "refresh_token_here",
157    ///     "your_client_id",
158    ///     "your_client_secret"
159    /// ).await?;
160    ///
161    /// println!("New access token: {}", response.access_token);
162    /// # Ok(())
163    /// # }
164    /// ```
165    pub async fn refresh_token(
166        &self,
167        refresh_token: &str,
168        client_id: &str,
169        client_secret: &str,
170    ) -> Result<OpenIDTokenResponse> {
171        let request = OpenIDTokenRequest {
172            code: String::new(),
173            client_id: client_id.to_string(),
174            client_secret: client_secret.to_string(),
175            redirect_uri: None,
176            grant_type: Some("refresh_token".to_string()),
177            refresh_token: Some(refresh_token.to_string()),
178        };
179        self.client.post("openid.connect.token", &request).await
180    }
181
182    /// Get user identity information
183    ///
184    /// Retrieve information about the authenticated user using an access token
185    /// obtained from the OpenID Connect flow.
186    ///
187    /// # Example
188    ///
189    /// ```no_run
190    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191    /// # use slacko::{SlackClient, AuthConfig};
192    /// # let client = SlackClient::new(AuthConfig::bot("xoxb-token"))?;
193    /// let openid = client.openid();
194    ///
195    /// // Use the access token from token exchange
196    /// let user_info = openid.user_info().await?;
197    ///
198    /// println!("User ID: {}", user_info.sub);
199    /// println!("Email: {:?}", user_info.email);
200    /// println!("Name: {:?}", user_info.name);
201    /// println!("Team: {:?}", user_info.team_name);
202    /// # Ok(())
203    /// # }
204    /// ```
205    pub async fn user_info(&self) -> Result<UserInfoResponse> {
206        self.client.post("openid.connect.userInfo", &()).await
207    }
208}