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-axum = "0.49.0"
|
||||||
shuttle-runtime = "0.49.0"
|
shuttle-runtime = "0.49.0"
|
||||||
tokio = "1.28.2"
|
tokio = "1.28.2"
|
||||||
|
jsonwebtoken = { version = "9.3.0", optional = true }
|
||||||
cargo-manifest = { version = "0.17.0", optional = true }
|
cargo-manifest = { version = "0.17.0", optional = true }
|
||||||
serde_yml = { version = "0.0.12", optional = true }
|
serde_yml = { version = "0.0.12", optional = true }
|
||||||
toml = { version = "0.8.19", optional = true }
|
toml = { version = "0.8.19", optional = true }
|
||||||
rand = { version = "0.8.5", 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]
|
[dev-dependencies]
|
||||||
axum-test = "16.4.0"
|
axum-test = "16.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["jsonwebtoken"]
|
||||||
task1-9 = ["cargo-manifest", "serde_yml", "toml"]
|
task1-9 = ["cargo-manifest", "serde_yml", "toml"]
|
||||||
task12 = ["rand"]
|
task12 = ["rand"]
|
||||||
task16 = []
|
task16 = ["jsonwebtoken"]
|
||||||
task19 = []
|
task19 = []
|
||||||
task23 = []
|
task23 = []
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
// use axum::routing::{get, post};
|
|
||||||
#[cfg(feature = "task12")]
|
#[cfg(feature = "task12")]
|
||||||
use routes::{board, place, random_board, reset, Board};
|
use routes::{board, place, random_board, reset, Board};
|
||||||
|
use routes::{decode, unwrap, wrap};
|
||||||
#[cfg(feature = "task1-9")]
|
#[cfg(feature = "task1-9")]
|
||||||
use routes::{
|
use routes::{
|
||||||
hello_bird, hello_world, ipv4_dest, ipv4_key, ipv6_dest, ipv6_key, manifest, milk, minus_one,
|
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());
|
.with_state(Board::new());
|
||||||
|
|
||||||
Router::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")]
|
#[cfg(feature = "task12")]
|
||||||
pub use task_twelve::{board, game::Board, place, random_board, reset};
|
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")]
|
#[cfg(feature = "task1-9")]
|
||||||
mod hello_bird;
|
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) {
|
pub fn random(&self, random: &mut StdRng) {
|
||||||
let mut columns: [[Option<Slot>; 4]; 4] = [[None; 4]; 4];
|
let mut columns: [[Option<Slot>; 4]; 4] = [[None; 4]; 4];
|
||||||
// let mut seed = self.seed.lock().unwrap();
|
|
||||||
for i in (0..4).rev() {
|
for i in (0..4).rev() {
|
||||||
(0..4).for_each(|j| {
|
(0..4).for_each(|j| {
|
||||||
let random = random.gen::<bool>();
|
let random = random.gen::<bool>();
|
||||||
@ -54,17 +53,6 @@ impl Board {
|
|||||||
columns[j][i] = slot;
|
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();
|
let mut cols = self.columns.lock().unwrap();
|
||||||
|
@ -9,20 +9,3 @@ const EMPTY: &str = "⬛";
|
|||||||
const COOKIE: &str = "🍪";
|
const COOKIE: &str = "🍪";
|
||||||
const MILK: &str = "🥛";
|
const MILK: &str = "🥛";
|
||||||
const WALL: &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)]
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn random_board(State(board): State<Board>) -> impl IntoResponse {
|
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 seed = board.get_seed();
|
||||||
let mut random = seed.lock().unwrap();
|
let mut random = seed.lock().unwrap();
|
||||||
board.random(&mut random);
|
board.random(&mut random);
|
||||||
|
@ -128,10 +128,9 @@ No winner.
|
|||||||
let response = server.post("/12/place/cookie/3").await;
|
let response = server.post("/12/place/cookie/3").await;
|
||||||
response.assert_status_ok();
|
response.assert_status_ok();
|
||||||
|
|
||||||
// for i in 4..5 {
|
|
||||||
let response = server.post("/12/place/milk/4").await;
|
let response = server.post("/12/place/milk/4").await;
|
||||||
response.assert_status_ok();
|
response.assert_status_ok();
|
||||||
// }
|
|
||||||
let response = server.post("/12/place/cookie/4").await;
|
let response = server.post("/12/place/cookie/4").await;
|
||||||
response.assert_status_ok();
|
response.assert_status_ok();
|
||||||
|
|
||||||
@ -154,7 +153,7 @@ No winner.
|
|||||||
⬜⬜⬜⬜⬜⬜
|
⬜⬜⬜⬜⬜⬜
|
||||||
";
|
";
|
||||||
let response = server.get("/12/random-board").await;
|
let response = server.get("/12/random-board").await;
|
||||||
// dbg!(response.text());
|
|
||||||
response.assert_status_ok();
|
response.assert_status_ok();
|
||||||
response.assert_text(want);
|
response.assert_text(want);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user