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
|
//!
//! This example showcases the Github OAuth2 process for requesting access to the user's public repos and
//! email address.
//!
//! Before running it, you'll need to generate your own Github OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github
//! ```
//!
//! ...and follow the instructions.
//!
use oauth2::basic::BasicClient;
use oauth2::reqwest;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
TokenResponse, TokenUrl,
};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use url::Url;
use std::env;
#[tokio::main]
async fn main() {
let github_client_id = ClientId::new(
env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."),
);
let github_client_secret = ClientSecret::new(
env::var("GITHUB_CLIENT_SECRET")
.expect("Missing the GITHUB_CLIENT_SECRET environment variable."),
);
let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())
.expect("Invalid authorization endpoint URL");
let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())
.expect("Invalid token endpoint URL");
// Set up the config for the Github OAuth2 process.
let client = BasicClient::new(github_client_id)
.set_client_secret(github_client_secret)
.set_auth_uri(auth_url)
.set_token_uri(token_url)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
);
let http_client = reqwest::ClientBuilder::new()
// Following redirects opens the client up to SSRF vulnerabilities.
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Client should build");
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state) = client
.authorize_url(CsrfToken::new_random)
// This example is requesting access to the user's public repos and email.
.add_scope(Scope::new("public_repo".to_string()))
.add_scope(Scope::new("user:email".to_string()))
.url();
println!("Open this URL in your browser:\n{authorize_url}\n");
let (code, state) = {
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
loop {
if let Ok((mut stream, _)) = listener.accept().await {
let mut reader = BufReader::new(&mut stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).await.unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code = url
.query_pairs()
.find(|(key, _)| key == "code")
.map(|(_, code)| AuthorizationCode::new(code.into_owned()))
.unwrap();
let state = url
.query_pairs()
.find(|(key, _)| key == "state")
.map(|(_, state)| CsrfToken::new(state.into_owned()))
.unwrap();
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).await.unwrap();
// The server will terminate itself after collecting the first code.
break (code, state);
}
}
};
println!("Github returned the following code:\n{}\n", code.secret());
println!(
"Github returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_res = client.exchange_code(code).request_async(&http_client).await;
println!("Github returned the following token:\n{token_res:?}\n");
if let Ok(token) = token_res {
// NB: Github returns a single comma-separated "scope" parameter instead of multiple
// space-separated scopes. Github-specific clients can parse this scope into
// multiple scopes by splitting at the commas. Note that it's not safe for the
// library to do this by default because RFC 6749 allows scopes to contain commas.
let scopes = if let Some(scopes_vec) = token.scopes() {
scopes_vec
.iter()
.flat_map(|comma_separated| comma_separated.split(','))
.collect::<Vec<_>>()
} else {
Vec::new()
};
println!("Github returned the following scopes:\n{scopes:?}\n");
}
}
|