Deserializing lifetime problems

We're trying to help.

Since you pasted a large block of code earlier, here is how I have changed it to own the stuff that is deserialized.

use rocket::serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use tokio::sync::RwLock;

use oxide_auth::endpoint::UniqueValue;
use reqwest::{header, Response};

#[derive(Clone)]
pub struct Client<'r> {
    config: Config<'r>,
    state: Arc<RwLock<State>>,
}

unsafe impl<'r> Send for Client<'r> {}
unsafe impl<'r> Sync for Client<'r> {}

#[derive(Clone, Copy)]
pub struct Config<'r> {
    pub protected_url: &'r str,
    pub token_url: &'r str,
    pub refresh_url: &'r str,
    pub client_id: &'r str,
    pub redirect_uri: &'r str,
    pub client_secret: Option<&'r str>,
}

unsafe impl<'r> Send for Config<'r> {}
unsafe impl<'r> Sync for Config<'r> {}

pub enum Error {
    AccessFailed,
    NoToken,
    AuthorizationFailed,
    RefreshFailed,
    Invalid(serde_json::Error),
    MissingToken,
    Response(String),
}

unsafe impl Send for Error {}
unsafe impl Sync for Error {}

#[derive(Debug, Default, Clone)]
pub struct State {
    pub token: Option<String>,
    pub refresh: Option<String>,
    pub until: Option<i64>,
}

unsafe impl Send for State {}
unsafe impl Sync for State {}

#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
pub struct TokenMap {
    token_type: String,

    scope: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    access_token: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    refresh_token: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    expires_in: Option<i64>,

    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<String>,
}

impl<'r> Client<'r> {
    pub fn new(config: Config<'r>) -> Self {
        Client {
            config,
            state: Arc::new(RwLock::new(State::default())),
        }
    }

    pub async fn authorize<'a>(&self, code: &'a str) -> Result<(), Error> {
        let client = reqwest::Client::new();
        let mut state = self.state.write().await;

        let mut params = HashMap::new();
        params.insert("grant_type", "authorization_code");
        params.insert("code", code);
        params.insert("redirect_uri", &self.config.redirect_uri);

        let access_token_request = match &self.config.client_secret {
            Some(client_secret) => client
                .post(self.config.token_url)
                .form(&params)
                .basic_auth(&self.config.client_id, client_secret.get_unique())
                .build()
                .unwrap(),

            None => {
                params.insert("client_id", &self.config.client_id);
                client
                    .post(self.config.token_url)
                    .form(&params)
                    .build()
                    .unwrap()
            }
        };

        let token_response = client
            .execute(access_token_request)
            .await
            .map_err(|_| Error::AuthorizationFailed)?;

        let token_map: TokenMap = parse_token_response(token_response).await.unwrap();

        if let Some(err) = token_map.error {
            return Err(Error::Response(err.to_string()));
        }

        if let Some(token) = token_map.access_token {
            state.token = Some(token);
            state.refresh = token_map.refresh_token;
            state.until = token_map.expires_in;
            return Ok(());
        }

        Err(Error::MissingToken)
    }

    pub async fn retrieve_protected_page(&self) -> Result<String, Error> {
        let client = reqwest::Client::new();

        let state = self.state.read().await;
        let token = match &state.token {
            Some(token) => token,
            None => return Err(Error::NoToken),
        };

        // Request the page with the oauth token
        let page_request = client
            .get(self.config.protected_url)
            .header(header::AUTHORIZATION, "Bearer ".to_string() + &token)
            .build()
            .unwrap();

        let page_response = match client.execute(page_request).await {
            Ok(response) => response,
            Err(_) => return Err(Error::AccessFailed),
        };

        let protected_page = page_response.text().await.unwrap();

        Ok(protected_page)
    }

    pub async fn refresh(&self) -> Result<(), Error> {
        let client = reqwest::Client::new();

        let mut state = self.state.write().await;
        let refresh = match state.refresh {
            Some(ref refresh) => refresh.clone(),
            None => return Err(Error::NoToken),
        };

        let mut params = HashMap::new();
        params.insert("grant_type", "refresh_token");
        params.insert("refresh_token", &refresh);

        let access_token_request = match &self.config.client_secret {
            Some(client_secret) => client
                .post(self.config.refresh_url)
                .form(&params)
                .basic_auth(&self.config.client_id, client_secret.get_unique())
                .build()
                .unwrap(),
            None => {
                params.insert("client_id", &self.config.client_id);
                client
                    .post(self.config.refresh_url)
                    .form(&params)
                    .build()
                    .unwrap()
            }
        };

        let token_response = client
            .execute(access_token_request)
            .await
            .map_err(|_| Error::RefreshFailed)?;

        let token_map: TokenMap = parse_token_response(token_response).await.unwrap();

        if token_map.error.is_some() || !token_map.access_token.is_some() {
            return Err(Error::MissingToken);
        }

        let token = token_map.access_token.unwrap();
        state.token = Some(token);
        state.refresh = token_map.refresh_token.or(state.refresh.take());
        state.until = token_map.expires_in;
        Ok(())
    }

    pub async fn as_html(&self) -> String {
        format!("{}", self.state.read().await)
    }
}

pub async fn parse_token_response<'r>(response: Response) -> Result<TokenMap, serde_json::Error> {
    let tkn = response.text().await;
    let token = tkn.unwrap();
    let res: Result<TokenMap, serde_json::Error> =
        serde_json::from_str(&token /* <<< here is the error */);

    match res {
        Ok(val) => return Ok(val.clone()),
        Err(e) => return Err(e),
    };
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Invalid(err)
    }
}

impl<'r> fmt::Display for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Token {<br>")?;
        write!(f, "&nbsp;token: {:?},<br>", self.token)?;
        write!(f, "&nbsp;refresh: {:?},<br>", self.refresh)?;
        write!(f, "&nbsp;expires_in: {:?},<br>", self.until)?;
        f.write_str("}")
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::AuthorizationFailed => f.write_str("Could not fetch bearer token"),
            Error::NoToken => f.write_str("No token with which to access protected page"),
            Error::AccessFailed => {
                f.write_str("Access token failed to authorize for protected page")
            }
            Error::RefreshFailed => f.write_str("Could not refresh bearer token"),
            Error::Invalid(serde) => write!(f, "Bad json response: {}", serde),
            Error::MissingToken => write!(f, "No token nor error in server response"),
            Error::Response(err) => write!(f, "Server error while fetching token: {}", err),
        }
    }
}

TokenMap does not need a lifetime or any borrowing. Arguably your other structs should also not store any borrows.

1 Like

I know you're trying to help, I just don't understand very well. I'm trying your solution right now.

EDIT: Just noticed I ran out of replies for the day. :frowning:

EDIT 2: It works!!! Thank you so much!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.