github.rs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. //!
  2. //! This example showcases the Github OAuth2 process for requesting access to the user's public repos and
  3. //! email address.
  4. //!
  5. //! Before running it, you'll need to generate your own Github OAuth2 credentials.
  6. //!
  7. //! In order to run the example call:
  8. //!
  9. //! ```sh
  10. //! GITHUB_CLIENT_ID=xxx GITHUB_CLIENT_SECRET=yyy cargo run --example github
  11. //! ```
  12. //!
  13. //! ...and follow the instructions.
  14. //!
  15. use oauth2::basic::BasicClient;
  16. // Alternatively, this can be `oauth2::curl::http_client` or a custom client.
  17. use oauth2::reqwest::http_client;
  18. use oauth2::{
  19. AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
  20. TokenResponse, TokenUrl,
  21. };
  22. use std::env;
  23. use std::io::{BufRead, BufReader, Write};
  24. use std::net::TcpListener;
  25. use url::Url;
  26. fn main() {
  27. let github_client_id = ClientId::new(
  28. env::var("GITHUB_CLIENT_ID").expect("Missing the GITHUB_CLIENT_ID environment variable."),
  29. );
  30. let github_client_secret = ClientSecret::new(
  31. env::var("GITHUB_CLIENT_SECRET")
  32. .expect("Missing the GITHUB_CLIENT_SECRET environment variable."),
  33. );
  34. let auth_url = AuthUrl::new("https://github.com/login/oauth/authorize".to_string())
  35. .expect("Invalid authorization endpoint URL");
  36. let token_url = TokenUrl::new("https://github.com/login/oauth/access_token".to_string())
  37. .expect("Invalid token endpoint URL");
  38. // Set up the config for the Github OAuth2 process.
  39. let client = BasicClient::new(
  40. github_client_id,
  41. Some(github_client_secret),
  42. auth_url,
  43. Some(token_url),
  44. )
  45. // This example will be running its own server at localhost:8080.
  46. // See below for the server implementation.
  47. .set_redirect_uri(
  48. RedirectUrl::new("http://localhost:8080".to_string()).expect("Invalid redirect URL"),
  49. );
  50. // Generate the authorization URL to which we'll redirect the user.
  51. let (authorize_url, csrf_state) = client
  52. .authorize_url(CsrfToken::new_random)
  53. // This example is requesting access to the user's public repos and email.
  54. .add_scope(Scope::new("public_repo".to_string()))
  55. .add_scope(Scope::new("user:email".to_string()))
  56. .url();
  57. println!(
  58. "Open this URL in your browser:\n{}\n",
  59. authorize_url.to_string()
  60. );
  61. // A very naive implementation of the redirect server.
  62. let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
  63. for stream in listener.incoming() {
  64. if let Ok(mut stream) = stream {
  65. let code;
  66. let state;
  67. {
  68. let mut reader = BufReader::new(&stream);
  69. let mut request_line = String::new();
  70. reader.read_line(&mut request_line).unwrap();
  71. let redirect_url = request_line.split_whitespace().nth(1).unwrap();
  72. let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
  73. let code_pair = url
  74. .query_pairs()
  75. .find(|pair| {
  76. let &(ref key, _) = pair;
  77. key == "code"
  78. })
  79. .unwrap();
  80. let (_, value) = code_pair;
  81. code = AuthorizationCode::new(value.into_owned());
  82. let state_pair = url
  83. .query_pairs()
  84. .find(|pair| {
  85. let &(ref key, _) = pair;
  86. key == "state"
  87. })
  88. .unwrap();
  89. let (_, value) = state_pair;
  90. state = CsrfToken::new(value.into_owned());
  91. }
  92. let message = "Go back to your terminal :)";
  93. let response = format!(
  94. "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
  95. message.len(),
  96. message
  97. );
  98. stream.write_all(response.as_bytes()).unwrap();
  99. println!("Github returned the following code:\n{}\n", code.secret());
  100. println!(
  101. "Github returned the following state:\n{} (expected `{}`)\n",
  102. state.secret(),
  103. csrf_state.secret()
  104. );
  105. // Exchange the code with a token.
  106. let token_res = client.exchange_code(code).request(http_client);
  107. println!("Github returned the following token:\n{:?}\n", token_res);
  108. if let Ok(token) = token_res {
  109. // NB: Github returns a single comma-separated "scope" parameter instead of multiple
  110. // space-separated scopes. Github-specific clients can parse this scope into
  111. // multiple scopes by splitting at the commas. Note that it's not safe for the
  112. // library to do this by default because RFC 6749 allows scopes to contain commas.
  113. let scopes = if let Some(scopes_vec) = token.scopes() {
  114. scopes_vec
  115. .iter()
  116. .map(|comma_separated| comma_separated.split(','))
  117. .flatten()
  118. .collect::<Vec<_>>()
  119. } else {
  120. Vec::new()
  121. };
  122. println!("Github returned the following scopes:\n{:?}\n", scopes);
  123. }
  124. // The server will terminate itself after collecting the first code.
  125. break;
  126. }
  127. }
  128. }