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