challenge: 16
This commit is contained in:
parent
81263f7803
commit
ff4210d032
@ -10,18 +10,22 @@ serde_json = "1.0.133"
|
||||
shuttle-axum = "0.49.0"
|
||||
shuttle-runtime = "0.49.0"
|
||||
tokio = "1.28.2"
|
||||
jsonwebtoken = { version = "9.3.0", optional = true }
|
||||
cargo-manifest = { version = "0.17.0", optional = true }
|
||||
serde_yml = { version = "0.0.12", optional = true }
|
||||
toml = { version = "0.8.19", optional = true }
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
axum-extra = { version = "0.9.6", features = ["cookie"] }
|
||||
chrono = "0.4.39"
|
||||
tracing = "0.1.41"
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "16.4.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["jsonwebtoken"]
|
||||
task1-9 = ["cargo-manifest", "serde_yml", "toml"]
|
||||
task12 = ["rand"]
|
||||
task16 = []
|
||||
task16 = ["jsonwebtoken"]
|
||||
task19 = []
|
||||
task23 = []
|
||||
|
@ -1,8 +1,8 @@
|
||||
mod routes;
|
||||
|
||||
// use axum::routing::{get, post};
|
||||
#[cfg(feature = "task12")]
|
||||
use routes::{board, place, random_board, reset, Board};
|
||||
use routes::{decode, unwrap, wrap};
|
||||
#[cfg(feature = "task1-9")]
|
||||
use routes::{
|
||||
hello_bird, hello_world, ipv4_dest, ipv4_key, ipv6_dest, ipv6_key, manifest, milk, minus_one,
|
||||
@ -42,4 +42,7 @@ pub fn router() -> axum::Router {
|
||||
.with_state(Board::new());
|
||||
|
||||
Router::new()
|
||||
.route("/16/wrap", post(wrap))
|
||||
.route("/16/unwrap", get(unwrap))
|
||||
.route("/16/decode", post(decode))
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ mod task_twelve;
|
||||
#[cfg(feature = "task12")]
|
||||
pub use task_twelve::{board, game::Board, place, random_board, reset};
|
||||
|
||||
// #[cfg(feature = "task16")]
|
||||
mod task_sixteen;
|
||||
pub use task_sixteen::{decode, unwrap, wrap};
|
||||
|
||||
#[cfg(feature = "task1-9")]
|
||||
mod hello_bird;
|
||||
|
||||
|
41
src/routes/task_sixteen/claims.rs
Normal file
41
src/routes/task_sixteen/claims.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
exp: Option<i64>,
|
||||
#[serde(rename = "sub")]
|
||||
sub: Option<String>,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
pub fn new(sub: Option<String>) -> Self {
|
||||
let exp = Some((Utc::now() + Duration::minutes(15)).timestamp());
|
||||
Self { exp, sub }
|
||||
}
|
||||
|
||||
pub fn sub(&self) -> Option<String> {
|
||||
self.sub.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Claims {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self.exp {
|
||||
Some(exp) => write!(
|
||||
f,
|
||||
"expires: {:?}\nsub: {}",
|
||||
exp,
|
||||
self.sub.as_deref().unwrap_or("None")
|
||||
),
|
||||
None => write!(
|
||||
f,
|
||||
"expires: None\nsub: {}",
|
||||
self.sub.as_deref().unwrap_or("None")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
57
src/routes/task_sixteen/decode.rs
Normal file
57
src/routes/task_sixteen/decode.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
|
||||
use serde_json::Value;
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
#[instrument(skip(payload))]
|
||||
pub async fn decode(payload: String) -> Result<String, Response> {
|
||||
info!("payload" = payload);
|
||||
|
||||
let decoding_key = DecodingKey::from_rsa_pem(
|
||||
"-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs5BlLjDtKuEY2NV3+xhH
|
||||
WWlKrZDWkIOV+HoLURIBEpAHa11xU+wL9sySR17j4bL9MJawlCJAGArW8vnDiAv8
|
||||
366PfOhCqZsD9N2iG28y7vf5q1PhoXl/Vfuelykw0k+r4054h0uCg9Olal0Nm/V8
|
||||
vsdPEC3wjNLBi86oYESkW43/7lbBWPBti1POCVJDuBEASZFhIR2+mfz6AFWQwmqO
|
||||
zzhP1Yli/7EtNMELWezQJXnVLQ3JvjT2btWWwKYT468YX/NtQgMC7SLvIRBuWb/Z
|
||||
ayfoi/9rGndqW0YPE1xwJEQA415w5HbfTneyAIxDy7TC8/+dFaKRcoPiEQA1T5bk
|
||||
OQIDAQAB
|
||||
-----END PUBLIC KEY-----"
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut validation = Validation::new(Algorithm::RS256);
|
||||
validation.validate_exp = true;
|
||||
validation.required_spec_claims = HashSet::new();
|
||||
validation.algorithms.push(Algorithm::RS512);
|
||||
let token = jsonwebtoken::decode::<Value>(&payload, &decoding_key, &validation);
|
||||
|
||||
match token {
|
||||
Ok(token) => {
|
||||
info!("token" = format!("{:?}", token));
|
||||
Ok(token.claims.to_string())
|
||||
}
|
||||
Err(e) => {
|
||||
if e.to_string().contains("InvalidSignature") {
|
||||
error!(
|
||||
"error" = format!("{}", e.to_string()),
|
||||
"status" = StatusCode::UNAUTHORIZED.to_string()
|
||||
);
|
||||
|
||||
return Err((StatusCode::UNAUTHORIZED, "").into_response());
|
||||
}
|
||||
error!(
|
||||
"error" = format!("{}", e.to_string()),
|
||||
"status" = StatusCode::BAD_REQUEST.to_string()
|
||||
);
|
||||
|
||||
return Err((StatusCode::BAD_REQUEST, "Token decoding error").into_response());
|
||||
}
|
||||
}
|
||||
}
|
7
src/routes/task_sixteen/mod.rs
Normal file
7
src/routes/task_sixteen/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod claims;
|
||||
mod decode;
|
||||
mod unwrap;
|
||||
mod wrap;
|
||||
pub use decode::decode;
|
||||
pub use unwrap::unwrap;
|
||||
pub use wrap::wrap;
|
35
src/routes/task_sixteen/unwrap.rs
Normal file
35
src/routes/task_sixteen/unwrap.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use jsonwebtoken::{DecodingKey, Validation};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use super::claims::Claims;
|
||||
|
||||
#[instrument(skip(jar))]
|
||||
pub async fn unwrap(jar: CookieJar) -> Result<String, Response> {
|
||||
let payload = jar
|
||||
.get("gift")
|
||||
.map(|cookie| cookie.value().to_string())
|
||||
.map(|token| {
|
||||
let token = jsonwebtoken::decode::<Claims>(
|
||||
&token,
|
||||
&DecodingKey::from_secret("secret".as_ref()),
|
||||
&Validation::default(),
|
||||
);
|
||||
token
|
||||
});
|
||||
|
||||
match payload {
|
||||
Some(Ok(claims)) => {
|
||||
info!("claims" = claims.claims.sub());
|
||||
claims
|
||||
.claims
|
||||
.sub()
|
||||
.map_or_else(|| Err((StatusCode::BAD_REQUEST, "").into_response()), Ok)
|
||||
}
|
||||
_ => Err((StatusCode::BAD_REQUEST, "").into_response()),
|
||||
}
|
||||
}
|
53
src/routes/task_sixteen/wrap.rs
Normal file
53
src/routes/task_sixteen/wrap.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use axum::{
|
||||
http::{header::CONTENT_TYPE, HeaderMap, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use serde_json::{json, Value};
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use super::claims::Claims;
|
||||
|
||||
#[instrument(skip(headers, jar))]
|
||||
pub async fn wrap(
|
||||
headers: HeaderMap,
|
||||
jar: CookieJar,
|
||||
Json(payload): Json<Value>,
|
||||
) -> Result<CookieJar, Response> {
|
||||
match headers.get(CONTENT_TYPE) {
|
||||
Some(content_type) => match content_type.to_str().unwrap() {
|
||||
"application/json" => (),
|
||||
_ => {
|
||||
return Err((StatusCode::UNSUPPORTED_MEDIA_TYPE, "").into_response());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err((
|
||||
StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
||||
"Content type not provided",
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
}
|
||||
|
||||
let payload_str = payload.to_string();
|
||||
warn!("{}", json!(&payload).to_string());
|
||||
info!("payload" = payload_str);
|
||||
let token = encode(
|
||||
&Header::default(),
|
||||
&Claims::new(Some(payload_str)),
|
||||
&EncodingKey::from_secret("secret".as_ref()),
|
||||
)
|
||||
.map_err(|e| {
|
||||
eprintln!("Error: {e}");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
|
||||
})?;
|
||||
|
||||
info!("token" = token);
|
||||
|
||||
let jar = jar.add(Cookie::new("gift", token));
|
||||
|
||||
Ok(jar)
|
||||
}
|
@ -42,7 +42,6 @@ impl Board {
|
||||
|
||||
pub fn random(&self, random: &mut StdRng) {
|
||||
let mut columns: [[Option<Slot>; 4]; 4] = [[None; 4]; 4];
|
||||
// let mut seed = self.seed.lock().unwrap();
|
||||
for i in (0..4).rev() {
|
||||
(0..4).for_each(|j| {
|
||||
let random = random.gen::<bool>();
|
||||
@ -54,17 +53,6 @@ impl Board {
|
||||
columns[j][i] = slot;
|
||||
});
|
||||
}
|
||||
// for column in &mut columns.iter_mut() {
|
||||
// for slot in column.iter_mut() {
|
||||
// let random = random.gen::<bool>();
|
||||
// if random {
|
||||
// *slot = Some(Slot::Cookie);
|
||||
// } else {
|
||||
// *slot = Some(Slot::Milk);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// drop(seed);
|
||||
|
||||
{
|
||||
let mut cols = self.columns.lock().unwrap();
|
||||
|
@ -9,20 +9,3 @@ const EMPTY: &str = "⬛";
|
||||
const COOKIE: &str = "🍪";
|
||||
const MILK: &str = "🥛";
|
||||
const WALL: &str = "⬜";
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_board() {
|
||||
// let board = Board::new();
|
||||
// println!("{board}");
|
||||
|
||||
// for _ in 0..4 {
|
||||
// assert!(board.insert(0, Slot::Milk).is_ok());
|
||||
// }
|
||||
|
||||
// println!("{board}");
|
||||
// }
|
||||
// }
|
||||
|
@ -36,7 +36,6 @@ pub async fn place(
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn random_board(State(board): State<Board>) -> impl IntoResponse {
|
||||
// let mut random = rand::rngs::StdRng::seed_from_u64(2024);
|
||||
let seed = board.get_seed();
|
||||
let mut random = seed.lock().unwrap();
|
||||
board.random(&mut random);
|
||||
|
@ -128,10 +128,9 @@ No winner.
|
||||
let response = server.post("/12/place/cookie/3").await;
|
||||
response.assert_status_ok();
|
||||
|
||||
// for i in 4..5 {
|
||||
let response = server.post("/12/place/milk/4").await;
|
||||
response.assert_status_ok();
|
||||
// }
|
||||
|
||||
let response = server.post("/12/place/cookie/4").await;
|
||||
response.assert_status_ok();
|
||||
|
||||
@ -154,7 +153,7 @@ No winner.
|
||||
⬜⬜⬜⬜⬜⬜
|
||||
";
|
||||
let response = server.get("/12/random-board").await;
|
||||
// dbg!(response.text());
|
||||
|
||||
response.assert_status_ok();
|
||||
response.assert_text(want);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user