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
use crate::api::github::{
    self,
    users::authenticated_user::{
        authenticated_user::{AuthenticatedUserViewer, Variables},
        AuthenticatedUser,
    },
};
use crate::api::rcos::users::accounts::reverse_lookup::ReverseLookup;
use crate::api::rcos::users::UserAccountType;
use crate::env::global_config;
use crate::error::TelescopeError;
use crate::web::services::auth::identity::{AuthenticationCookie, RootIdentity};
use crate::web::services::auth::oauth2_providers::{Oauth2Identity, Oauth2IdentityProvider};
use futures::future::LocalBoxFuture;
use oauth2::basic::{BasicClient, BasicTokenResponse};
use oauth2::{AccessToken, AuthUrl, Scope, TokenResponse, TokenUrl};
use std::sync::Arc;
use uuid::Uuid;

/// Zero sized type representing the GitHub OAuth2 identity provider.
pub struct GitHubOauth;

/// The identity object stored in the user's cookies for users signed in via
/// GitHub.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GitHubIdentity {
    /// The OAuth2 Access token granted by GitHub.
    pub access_token: AccessToken,
}

// Lazy static github client object.
lazy_static! {
    static ref GITHUB_CLIENT: Arc<BasicClient> = {
        // Get the global config.
        let config = global_config();

        // Create GitHub OAuth2 client.
        let github_client = BasicClient::new(
            config.github_credentials.client_id.clone(),
            Some(config.github_credentials.client_secret.clone()),
            AuthUrl::new("https://github.com/login/oauth/authorize".into())
                .expect("Invalid GitHub Auth URL"),
            Some(TokenUrl::new("https://github.com/login/oauth/access_token".into())
                .expect("Invalid GitHub Token URL")));
        // Return the client config wrapped in an Arc.
        Arc::new(github_client)
    };
}

impl Oauth2IdentityProvider for GitHubOauth {
    type IdentityType = GitHubIdentity;
    const SERVICE_NAME: &'static str = "github";

    fn get_client() -> Arc<BasicClient> {
        GITHUB_CLIENT.clone()
    }

    fn scopes() -> Vec<Scope> {
        vec![
            // Scope to read user's public profile information.
            Scope::new("read:user".into()),
            // Scope to read user's email address.
            //Scope::new("user:email".into()),
        ]
    }
}

impl Oauth2Identity for GitHubIdentity {
    const USER_ACCOUNT_TY: UserAccountType = UserAccountType::GitHub;

    fn from_basic_token(token: &BasicTokenResponse) -> Self {
        Self {
            access_token: token.access_token().clone(),
        }
    }

    fn platform_user_id(&self) -> LocalBoxFuture<Result<String, TelescopeError>> {
        Box::pin(async move { self.get_github_id().await })
    }

    fn into_root(self) -> RootIdentity {
        RootIdentity::GitHub(self)
    }

    fn add_to_cookie(self, cookie: &mut AuthenticationCookie) {
        cookie.github = Some(self);
    }
}

impl GitHubIdentity {
    /// Get the github account id of the user associated with this access token.
    /// Note that this is the GitHub GraphQL node ID, and is only compatible with the
    /// GitHub V4 API.
    pub async fn get_github_id(&self) -> Result<String, TelescopeError> {
        // Get the authenticated user and convert their id to a string.
        self.get_authenticated_user()
            .await
            .map(|u| u.id.to_string())
    }

    /// Get the authenticated GitHub user.
    pub async fn get_authenticated_user(&self) -> Result<AuthenticatedUserViewer, TelescopeError> {
        // Query the GitHub GraphQL API.
        github::send_query::<AuthenticatedUser>(&self.access_token, Variables {})
            // Wait for the response
            .await
            // Get the viewer from the response
            .map(|response| response.viewer)
    }

    /// Get the RCOS user ID of the authenticated user via their GitHub account on the central
    /// RCOS API.
    pub async fn get_rcos_user_id(&self) -> Result<Option<Uuid>, TelescopeError> {
        // Get the on platform id of this user.
        let platform_id: String = self.get_github_id().await?;
        // Send the query to the central RCOS API and await response.
        ReverseLookup::execute(UserAccountType::GitHub, platform_id).await
    }
}