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}