Coder Social home page Coder Social logo

Authorization about octocrab HOT 12 CLOSED

xampprocky avatar xampprocky commented on August 22, 2024
Authorization

from octocrab.

Comments (12)

rocallahan avatar rocallahan commented on August 22, 2024 2

I have a ton of octocrab changes queued up. I wanted to get my app working before I submitted them because it's really hard to test them in any other way :-(. They're here: https://github.com/Pernosco/octocrab/tree/events.
I'll rebase them and submit them.

from octocrab.

XAMPPRocky avatar XAMPPRocky commented on August 22, 2024 1

@aslamplr Thanks for sharing this, it's not directly applicable since Octocrab needs to work in non CLI projects such as servers, so things like opening the web browser wouldn't be possible, but it's a good place to start.

from octocrab.

chinedufn avatar chinedufn commented on August 22, 2024 1

@dkowis thanks you saved me some time.

In case this saves anyone else some time... here's a very quick very dirty example of authenticating as an installation built on top of dkowis' JWT snippet.

#[cfg(test)]
mod tests {
    use super::*;
    use crate::github::app_authentication::get_jwt;

    #[derive(Debug, Deserialize)]
    struct Installation {
        access_tokens_url: String,
    }

    #[derive(Debug, Deserialize)]
    struct Access {
        token: String,
    }

    #[derive(Debug, Serialize)]
    struct CreateAccessToken {
        repositories: Vec<String>,
    }

    #[actix_rt::test]
    async fn foo() {
        let jwt = get_jwt(APP_IDENTIFIER.to_string(), &PRIVATE_KEY).unwrap();
        let octo = octocrab::OctocrabBuilder::new()
            .personal_token(jwt)
            .build()
            .unwrap();
        let installations: Vec<Installation> =
            octo.get("/app/installations", None::<&()>).await.unwrap();

        let access: Access = octo
            .post(
                installations[0].access_tokens_url.as_str(),
                Some(&CreateAccessToken {
                    repositories: vec!["repo-name".to_string()],
                }),
            )
            .await
            .unwrap();

        let octo = octocrab::OctocrabBuilder::new()
            .personal_token(access.token)
            .build()
            .unwrap();

        let comment = octo
            .issues("account-name", "repo-name")
            .create_comment(121, "Created programatically!")
            .await
            .unwrap();
        dbg!(comment);
    }
}

from octocrab.

aslamplr avatar aslamplr commented on August 22, 2024

Hi 👋 I have a working implementation in Rust roughly based on the official GitHub’s cli project in Golang. It’s not refined or idiomatic Rust but works. https://github.com/aslamplr/gh-cli/blob/2dc15b56a3f233a5ae7f6fa33664fa995e072cbe/gh-auth/src/lib.rs#L6

from octocrab.

dkowis avatar dkowis commented on August 22, 2024

I have done a small bit of this, using a github app. Pasting it here for reference for others.

I used the oauth2 crate and jsonwebtoken crate. The client credentials grant allows my server side app to use the client ID/Secret, and the private key provided, to retrieve a token that can call github apis. This works if you plop it into the personal_token github call.

use jsonwebtoken::{EncodingKey, Algorithm, Header, encode};
use std::fs::File;
use std::io::prelude::*;
use serde::{Deserialize, Serialize};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use thiserror::Error;
use crate::lfr::github_auth::AuthError::{NoPrivateKeyFound, TokenError};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    iss: String,
    iat: usize,
    exp: usize,
}

#[derive(Error, Debug)]
pub enum AuthError {
    #[error("Unable to read private key")]
    NoPrivateKeyFound(#[from] std::io::Error),
    #[error("Unable to generate token")]
    TokenError(#[from] jsonwebtoken::errors::Error)
}


pub fn get_jwt(priv_key_path: &str) ->  Result<String, AuthError> {
    let mut f = File::open(priv_key_path).map_err(NoPrivateKeyFound)?;
    let mut buffer = Vec::new();
    f.read_to_end(&mut buffer)?;

    let key = EncodingKey::from_rsa_pem(buffer.as_slice())?;

    //Github needs an RS256 token
    //10 minute maximum token lifetime
    //issuer: 000000
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("Time Went Backwards...")
        .as_secs() as usize;
    let claims = Claims {
        iat: now,
        exp: now + (10 * 60),
        iss: "000000".to_string() //github app id
    };

    let header = Header::new(Algorithm::RS256);

    encode(&header, &claims, &key).map_err(TokenError)
}

This code is just how to get the app jwt which you can then use to get an installation token and use in a different octocrab client to make different API calls on the REPOs.

from octocrab.

rocallahan avatar rocallahan commented on August 22, 2024

I'm planning to implement this for Github Apps.

My API proposal is to add to Auth like so:

pub enum Auth {
    None,
    PersonalToken(String),
    App {
        app_id: String,
        key: jsonwebtoken::EncodingKey,
    },
}

with a new method on OctocrabBuilder, app(...). I also propose to add Octocrab::installation(InstallationId) -> Octocrab which returns a new client for a specific installation. This supports the usual workflow of "authenticate as Github App; look up an installation id using /repos/{owner}/{repo}/installation or something similar; then transition to working on that installation"

In the implementation, I would cache in Octocrab a String authentication token for the app, and a String authentication token for the installation, if applicable, renewing those tokens automatically as required. I'd use jsonwebtoken for JWT.

Does that sound good?

from octocrab.

XAMPPRocky avatar XAMPPRocky commented on August 22, 2024

with a new method on OctocrabBuilder, app(...). I also propose to add Octocrab::installation(InstallationId) -> Octocrab which returns a new client for a specific installation.

Hmm, I'm not sure on Octocrab::installation, admittedly I do not use GitHub from an installation, so if this useful for you let me know, but doesn't it not make sense to have it just accept InstallationId? Since that would require the installation being public, and I always assumed installations were private, and would also require auth.

In the implementation, I would cache in Octocrab a String authentication token for the app, and a String authentication token for the installation, if applicable, renewing those tokens automatically as required. I'd use jsonwebtoken for JWT.

How are we exposing the authentication workflow to the user? (re: the user of the library, not the GitHub user), I would want the solution to be compatible with clients which have no direct user input. So it needs to be programmatically accessible in some way.

from octocrab.

rocallahan avatar rocallahan commented on August 22, 2024

Hmm, I'm not sure on Octocrab::installation, admittedly I do not use GitHub from an installation, so if this useful for you let me know, but doesn't it not make sense to have it just accept InstallationId? Since that would require the installation being public, and I always assumed installations were private, and would also require auth.

For our app, authentication needs to use the following steps:

  • We get a Github event containing repository info (username/reponame)
  • We have to look up the installation id using https://docs.github.com/en/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app. This request must be authenticated via our app, so we create a JWT token using our app ID and secret key, and send it in the Bearer header.
  • Then we need to create an authentication token for that installation, using https://docs.github.com/en/rest/reference/apps#create-an-installation-access-token-for-an-app. This also requires authentication via our app so we create another JWT token for that (or reuse the previous one, but it's no trouble to create a fresh token each time here since it's just client-side computation).
  • Then we send various requests for that installation, authenticated using the previously acquired token, cached somewhere. Importantly, the installation token could expire (it's only valid for an hour), so we may need to repeat the previous step from time to time.

With the API I suggested this is easy to do from the library client's point of view:

  • Create an Octocrab passing the app ID and JWT secret in Auth::App
  • Send the "get repository installation" request using that Octocrab and extract the installation ID
  • Call Octocrab::installation(id) using that ID
  • Then using the new Octocrab, do the real work. At the first client API call it will create an installation token and cache it on the Octocrab. Whenever the token expires it will need to create a new one.

This puts responsibility for refreshing the installation token onto Octocrab. It puts responsibility for getting the installation ID onto the client, but that's inevitable because there are several different ways you might want to get that ID. It makes it seamless for clients to transition from the "look up the installation ID" phase to the "working with that installation" phase, passing Octocrab state along if necessary (e.g. the cached app JWT token if we wanted to cache that).

An obvious alternative design would be to drop Octocrab::installation(id) and instead have a 4th kind of Auth, e.g. Auth::Installation { app_id, secret_key, installation_id } and just create a fresh Octocrab after we've determined the installation ID. I'm fine with that, it's just a little more verbose from the client's point of view and makes it a little bit harder to reuse existing Octocrab state (if there is any).

from octocrab.

XAMPPRocky avatar XAMPPRocky commented on August 22, 2024

Ah okay, so if I so understand right the full signature is fn installation(self, installation_id: InstallationId) -> Self? In which case that seems fine to me, I only think it should be named something like into_installation_client. I think that more clearly communicates the intent.

from octocrab.

rocallahan avatar rocallahan commented on August 22, 2024

It doesn't consume self (the same app server might need to spawn multiple clients to work with different installations), so probably installation_client is better.

from octocrab.

XAMPPRocky avatar XAMPPRocky commented on August 22, 2024

Ah, true. That sounds good to me.

from octocrab.

XAMPPRocky avatar XAMPPRocky commented on August 22, 2024

@chinedufn Thank you for the snippet! I don’t know if don’t know if @rocallahan still intends to make a PR, but if not would you be up for making a PR adding those methods to the semantic API, and a authenticate_as_installation method that accepts a jwt and does what’s included in your snippet? Then everyone can use it in the library directly.

from octocrab.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.