Totally new to Rust. I am trying to implement oauth authentication and I am using axum, with no success.. Here is my ugly code:
use axum::{
Json,
extract::Query,
extract::Extension,
http::StatusCode,
response::IntoResponse
};
use serde_json::{json, Value};
use hyper;
use hyper_tls::HttpsConnector;
use hyper::header;
use cookie::Cookie;
use serde::{ Deserialize, Serialize };
#[derive(Clone)]
pub struct GitHubOAuth2 {
client_id: String,
redirect_uri: String,
client_secret: String
}
#[derive(Serialize, Deserialize)]
pub struct CallbackAuthCode {
code: String
}
impl GitHubOAuth2 {
pub fn new(conf: String) -> GitHubOAuth2 {
let json_content : Value = serde_json::from_str(&conf).expect("Invalid configuration.");
GitHubOAuth2 {
client_id: json_content["github_oauth2"]["client_id"].as_str().unwrap().to_string(),
redirect_uri: json_content["github_oauth2"]["redirect_uri"].as_str().unwrap().to_string(),
client_secret: json_content["github_oauth2"]["client_secret"].as_str().unwrap().to_string()
}
}
}
pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
let params_code = ¶ms.code;
let mut get_token_url: String = String::from("https://www.github.com/login/oauth/access_token?client_id=");
get_token_url.push_str(&conf.client_id);
get_token_url.push_str("&redirect_uri=");
get_token_url.push_str(&conf.redirect_uri);
get_token_url.push_str("&client_secret=");
get_token_url.push_str(&conf.client_secret);
get_token_url.push_str("&code=");
get_token_url.push_str(¶ms_code);
println!("get_token_url: {}", get_token_url);
let https = HttpsConnector::new();
let client = hyper::Client::builder().build::<_, hyper::Body>(https);
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(get_token_url)
.header("Accept", "application/json")
.body(hyper::Body::empty()).unwrap();
match client.request(req).await {
Ok(resp) => {
println!("response: {}", resp.status());
let redirectUri : String = resp.headers().get("Location").unwrap().to_str().unwrap().to_string();
if resp.status() == 301 {
let redirectReq = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(redirectUri)
.header("Accept", "application/json")
.body(hyper::Body::empty()).unwrap();
match client.request(redirectReq).await {
Ok(mut redirectResp) => {
let body = hyper::body::to_bytes(redirectResp.body_mut()).await.unwrap();
println!("{} {:?}", redirectResp.status(), body);
let body_as_json : Value = serde_json::from_slice(&body).unwrap();
let bearer_token = body_as_json["access_token"].as_str().unwrap().to_string();
let cookie = Cookie::build("hey", bearer_token).secure(true).http_only(true).finish();
return (
StatusCode::OK,
[(header::SET_COOKIE, &cookie.value())],
Json(json!({
"msg": "got that cookie"
}))
);
},
Err(mut redirect_e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
[(header::CONTENT_TYPE, &"application/json")],
Json(json!({
"error": redirect_e.to_string()
}))
);
}
}
} else {
return (
StatusCode::NOT_IMPLEMENTED,
[(header::CONTENT_TYPE, &"application/json")],
Json(json!({
"error": String::from("service replies with unexpected response.")
}))
);
}
},
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
[(header::CONTENT_TYPE, &"application/json")],
Json(json!({
"error": e.to_string()
}))
);
}
}
}
callback
function aims to implement the 'callback' phase in oauth2, so it picks the auth code and uses it to call github IdP to collect authorization token. No big deal. Problem is this code does not compile and I don't get why. Compiler says:
error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied
callback
function is attached to server as a get
handler.
I started from the axum basic examples and tried to build my monster one step at time but now I am stuck. What am I missing?
My Cargo.toml
:
[package]
name = "keymaster"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.5.16"
axum-macros = "0.2.3"
hyper = { version = "0.14.20", features = ["full"] }
tokio = { version = "1.21.2", features = ["full"] }
tower = "0.4.13"
serde_json = "1.0.85"
serde = "1.0.145"
jwt-simple = "0.10"
clap = { version = "4.0.19", features = ["derive"] }
hyper-tls = "0.5.0"
cookie = "0.17.0"
follow-redirects = "0.1.3"
http = "0.2.9"
[dependencies.uuid]
version = "1.2.1"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]
20230409 update
Removing &
from my &"application/json"
causes the compiler to report some more errors. The full log is here:
--> src/server/handlers/github_oauth2.rs:14:5
|
14 | use http::header::HeaderValue;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0308]: mismatched types
--> src/server/handlers/github_oauth2.rs:92:41
|
92 | [(header::CONTENT_TYPE, "application/json")],
| ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
|
= note: expected reference `&&str`
found reference `&'static str`
note: return type inferred to be `&&str` here
--> src/server/handlers/github_oauth2.rs:81:22
|
81 | return (
| ______________________^
82 | | StatusCode::OK,
83 | | [(header::SET_COOKIE, &cookie.value())],
84 | | Json(json!({
85 | | "msg": "got that cookie"
86 | | }))
87 | | );
| |_______________^
error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied
--> src/server/handlers/github_oauth2.rs:40:126
|
40 | pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
| ______________________________________________________________________________________________________________________________^
41 | | let params_code = ¶ms.code;
42 | |
43 | | let mut get_token_url: String = String::from("https://www.github.com/login/oauth/access_token?client_id=");
... |
118 | | }
119 | | }
| |_^ the trait `IntoResponse` is not implemented for `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>)`
|
= help: the following other types implement trait `IntoResponse`:
()
(Response<()>, R)
(Response<()>, T1, R)
(Response<()>, T1, T2, R)
(Response<()>, T1, T2, T3, R)
(Response<()>, T1, T2, T3, T4, R)
(Response<()>, T1, T2, T3, T4, T5, R)
(Response<()>, T1, T2, T3, T4, T5, T6, R)
and 60 others
error[E0308]: mismatched types
--> src/server/handlers/github_oauth2.rs:102:35
|
102 | [(header::CONTENT_TYPE, "application/json")],
| ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
|
= note: expected reference `&&str`
found reference `&'static str`
note: return type inferred to be `&&str` here
--> src/server/handlers/github_oauth2.rs:81:22
|
81 | return (
| ______________________^
82 | | StatusCode::OK,
83 | | [(header::SET_COOKIE, &cookie.value())],
84 | | Json(json!({
85 | | "msg": "got that cookie"
86 | | }))
87 | | );
| |_______________^
error[E0308]: mismatched types
--> src/server/handlers/github_oauth2.rs:112:33
|
112 | [(header::CONTENT_TYPE, "application/json")],
| ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
|
= note: expected reference `&&str`
found reference `&'static str`
note: return type inferred to be `&&str` here
--> src/server/handlers/github_oauth2.rs:81:22
|
81 | return (
| ______________________^
82 | | StatusCode::OK,
83 | | [(header::SET_COOKIE, &cookie.value())],
84 | | Json(json!({
85 | | "msg": "got that cookie"
86 | | }))
87 | | );
| |_______________^
error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied
--> src/server/handlers/github_oauth2.rs:40:108
|
40 | pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
| ^^^^^^^^^^^^^^^^^ the trait `IntoResponse` is not implemented for `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>)`
|
= help: the following other types implement trait `IntoResponse`:
()
(Response<()>, R)
(Response<()>, T1, R)
(Response<()>, T1, T2, R)
(Response<()>, T1, T2, T3, R)
(Response<()>, T1, T2, T3, T4, R)
(Response<()>, T1, T2, T3, T4, T5, R)
(Response<()>, T1, T2, T3, T4, T5, T6, R)
and 60 others
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
warning: `keymaster` (bin "keymaster") generated 1 warning
error: could not compile `keymaster` due to 5 previous errors; 1 warning emitted