use std::fmt;
use crate::crypto::zeroize::Zeroizing;
use crate::encoding::url_encode_component;
use crate::util::log::debug;
use super::config::OAuthConfig;
#[doc(alias = "refresh")]
pub struct RefreshRequest {
body: Zeroizing<String>,
content_type: &'static str,
}
impl RefreshRequest {
#[must_use]
pub fn new(config: &OAuthConfig, refresh_token: &str) -> Self {
let mut body = String::with_capacity(256);
body.push_str("grant_type=refresh_token");
body.push_str("&refresh_token=");
body.push_str(&url_encode_component(refresh_token));
body.push_str("&client_id=");
body.push_str(&url_encode_component(config.client_id()));
body.push_str("&client_secret=");
body.push_str(&url_encode_component(config.client_secret()));
debug!("oauth: refresh request built");
Self {
body: Zeroizing::new(body),
content_type: "application/x-www-form-urlencoded",
}
}
#[must_use]
#[inline]
pub fn body(&self) -> &str {
&self.body
}
#[must_use]
#[inline]
pub fn content_type(&self) -> &str {
self.content_type
}
}
impl fmt::Debug for RefreshRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RefreshRequest")
.field("body", &"[REDACTED]")
.field("content_type", &self.content_type)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oauth::config::OAuthConfig;
fn test_config() -> OAuthConfig {
OAuthConfig::builder("my-client-id", "my-secret")
.authorization_endpoint("https://auth.example.com/authorize")
.token_endpoint("https://auth.example.com/token")
.redirect_uri("https://myapp.example.com/callback")
.scope("openid")
.build()
.unwrap()
}
#[test]
fn body_contains_grant_type() {
let config = test_config();
let req = RefreshRequest::new(&config, "refresh-token-123");
assert!(
req.body().contains("grant_type=refresh_token"),
"body should contain grant_type=refresh_token: {}",
req.body(),
);
}
#[test]
fn body_contains_refresh_token() {
let config = test_config();
let req = RefreshRequest::new(&config, "refresh-token-123");
assert!(
req.body().contains("refresh_token=refresh-token-123"),
"body should contain refresh_token: {}",
req.body(),
);
}
#[test]
fn body_contains_client_id() {
let config = test_config();
let req = RefreshRequest::new(&config, "refresh-token-123");
assert!(
req.body().contains("client_id=my-client-id"),
"body should contain client_id: {}",
req.body(),
);
}
#[test]
fn body_contains_client_secret() {
let config = test_config();
let req = RefreshRequest::new(&config, "refresh-token-123");
assert!(
req.body().contains("client_secret=my-secret"),
"body should contain client_secret: {}",
req.body(),
);
}
#[test]
fn content_type_is_form_urlencoded() {
let config = test_config();
let req = RefreshRequest::new(&config, "token");
assert_eq!(req.content_type(), "application/x-www-form-urlencoded",);
}
#[test]
fn refresh_token_with_special_chars_is_encoded() {
let config = test_config();
let req = RefreshRequest::new(&config, "token=with&special/chars");
assert!(
req.body()
.contains("refresh_token=token%3Dwith%26special%2Fchars"),
"refresh token with special characters should be URL-encoded: {}",
req.body(),
);
}
}