From 6cd6b12630527c100de983689d752fa5162fe4cf Mon Sep 17 00:00:00 2001 From: bitheaven Date: Mon, 20 May 2024 14:08:03 +0500 Subject: [PATCH] Move a lot funcs... --- TODO | 23 ++-- src/funcs.rs | 306 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 296 +-------------------------------------------- src/url.rs | 1 + src/url/login.rs | 124 +++++++++++++++++++ 5 files changed, 450 insertions(+), 300 deletions(-) create mode 100644 src/url/login.rs diff --git a/TODO b/TODO index 17c54c6..d1b816d 100644 --- a/TODO +++ b/TODO @@ -9,17 +9,26 @@ Auth using email -##### API v0 +##### API v0 (unsafe) # Auth link (device check url with this session and get data) -/v0/auth?session=&back_url= +/api/v0/auth?session=&back_url= # Get auth data -/v0/auth_finish?session= +/api/v0/auth_finish?session= -##### API v1 +##### API v1 (semi-safe) # Auth link -/v1/auth?session=&back_url= +/api/v1/auth?session=&back_url= # Get token -/v1/auth_finish?session= +/api/v1/auth_finish?session= # Refresh token -/v1/refresh?token= +/api/v1/refresh?token= + + +##### API v2 (safe) +# Auth link +/api/v2/auth?site_id=&session=&back_url=&webhook= +# Get token +/api/v2/auth_finish?site_id=&session= +# Refresh token +/api/v2/refresh?site_id=&token= diff --git a/src/funcs.rs b/src/funcs.rs index 5ee43f3..94d2d18 100644 --- a/src/funcs.rs +++ b/src/funcs.rs @@ -1,5 +1,6 @@ use { std::{ + sync::Arc, collections::HashMap, any::type_name, time::{ @@ -10,8 +11,54 @@ use { webhook::client::WebhookClient, urlencoding::decode as url_decode, uuid::Uuid, + chrono::DateTime, + serde_json::{ + Value as Json, + json, + }, + rsa::{ + pkcs1::{ + EncodeRsaPublicKey, + EncodeRsaPrivateKey, + }, + RsaPrivateKey, + RsaPublicKey, + }, + jsonwebtoken as jwt, + jwt::{ + Header, + Algorithm, + TokenData, + Validation, + EncodingKey, + DecodingKey, + encode as jwt_encode, + decode as jwt_decode, + }, + skytable::{ + query, + pool::ConnectionMgrTcp, + error::Error as SkyError, + }, + bb8::Pool, + http_body_util::BodyExt, + hyper::{ + Request, + HeaderMap, + body::Incoming, + header::{ + HeaderValue, + }, + }, crate::{ + Res, + Result, + DBPool, + REFRESH_LIFETIME, + TOKEN_LIFETIME, html::*, + Users, + Sites, }, }; @@ -29,6 +76,64 @@ pub fn build_html(body: &str) -> String { format!("{}{}{}{}", HEADER_HTML, CSS3, body, FOOTER_HTML) } +pub async fn init_tables(pool: DBPool) -> Res<(), SkyError> { + let mut con = pool.get().await.unwrap(); + + let _ = con.query_parse::<()>(&query!("CREATE SPACE IF NOT EXISTS bitauth")).await; + let _ = con.query_parse::<()>(&query!(r#" + CREATE MODEL IF NOT EXISTS bitauth.users( + uuid: string, + login: string, + password: string, + email: string, + tokens: list {type: string} + ) + "#)).await; + let _ = con.query_parse::<()>(&query!(r#" + CREATE MODEL IF NOT EXISTS bitauth.users_uuid( + login: string, + uuid: string + ) + "#)).await; + let _ = con.query_parse::<()>(&query!(r#" + CREATE MODEL IF NOT EXISTS bitauth.sites( + uuid: string, + uid: string, + domain: string, + pkey: binary, + skey: binary + ) + "#)).await; + let _ = con.query_parse::<()>(&query!(r#" + CREATE MODEL IF NOT EXISTS bitauth.tokens( + uuid: string, + uid: string, + sid: string, + ref: string, + refend: uint32 + ) + "#)).await; + let _ = con.query_parse::<()>(&query!(r#" + CREATE MODEL IF NOT EXISTS bitauth.v0( + session: string, + login: string, + uuid: string, + expire: uint32 + ) + "#)).await; + + let q = con.query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = 0")).await; + if q.is_err() { + let (skey, pkey) = rsa_gen(); + let _ = con.query_parse::<()>(&query!( + "INSERT INTO bitauth.sites { uuid: ?, uid: ?, domain: ?, pkey: ?, skey: ? }", + "0", "0", "", pkey, skey + )).await; + }; + + Ok(()) +} + pub fn double_split(body: String, first: &str, second: &str) -> HashMap { body.split(first) .filter_map(|c| { @@ -72,3 +177,204 @@ pub async fn discord_wh_send(text: String) { ) ).await.unwrap(); } + +pub fn set_cookie(headers: &mut HeaderMap, key: &str, value: &str) { + let time = DateTime::from_timestamp((time() + REFRESH_LIFETIME) as i64, 0) + .expect("REASON") + .to_rfc2822(); + let time = time.replace("+0000", "GMT"); + headers.append( + hyper::header::SET_COOKIE, + HeaderValue::from_str(format!("{}={}; HttpOnly; Expires={}", key, value, time).as_str()) + .unwrap() + ); +} + +pub fn set_location(headers: &mut HeaderMap, location: &str) { + headers.append( + hyper::header::LOCATION, + HeaderValue::from_str(format!("{}", location).as_str()).unwrap() + ); +} + +pub async fn get_body_from_request(mut req: Request) -> Result> { + let mut body: Vec = vec![]; + while let Some(next) = req.frame().await { + let frame = next?; + if let Some(chunk) = frame.data_ref() { + body.extend_from_slice(chunk); + } + } + + Ok(body) +} + +pub async fn login_user(pool: DBPool, data: HashMap) -> Res<(String, String), SkyError> { + let mut ret: (String, String) = Default::default(); + let mut con = pool.get().await.unwrap(); + + let login = &data.get("login").unwrap().trim().to_lowercase(); + let pass = data.get("password").unwrap().trim(); + + let q = con + .query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login)) + .await; + if q.is_err() { println!("{:?}", q.err()); return Ok(ret); }; + let (uuid,) = q.unwrap(); + + let q = con + .query_parse::(&query!("SELECT * FROM bitauth.users WHERE uuid = ?", uuid.clone())) + .await; + if q.is_err() { + discord_wh_send( + format!("Пизда тут, ну тип, да, какая-то ебень с uuid, чел потерялся...\n`{}`\n**{}**", uuid, login) + ).await; + return Ok(ret); + } + + // TODO: Say to user about trouble. + + let q = q.unwrap(); + if bcrypt::verify(pass, q.password.as_str()).unwrap() { + let token = format!("{}", uuid_v4().as_hyphenated()); + let reftoken = format!("{}", uuid_v4().as_hyphenated()); + let time = time(); + let uuid: String = q.uuid; + let login: String = q.login; + + let _ = con.query_parse::<()>(&query!( + "INSERT INTO bitauth.tokens { uuid: ?, uid: ?, sid: ?, ref: ?, refend: ? }", + token.clone(), uuid.clone(), "0", reftoken.clone(), time + REFRESH_LIFETIME + )).await; + let _ = con.query_parse::<()>(&query!( + "UPDATE bitauth.users SET tokens += ? WHERE login = ?", + token.clone(), login.clone() + )).await; + + ret = ( + jwt_sign(pool.clone(), json!({ + "login": login.clone(), + "uuid": uuid.clone(), + "iat": time, + "exp": time + TOKEN_LIFETIME + })).await.unwrap(), + jwt_sign(pool.clone(), json!({ + "uuid": token.clone(), + "iat": time, + "ref": reftoken.clone(), + "exp": time + REFRESH_LIFETIME + })).await.unwrap() + ); + } + + Ok(ret) +} + +pub async fn jwt_sign(pool: DBPool, data: Json) -> Result { + let mut con = pool.get().await.unwrap(); + + let q = con + .query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0")) + .await; + if q.is_err() { return Ok("".to_owned()) }; + let skey = q.unwrap().skey; + + let skey = EncodingKey::from_rsa_der(&skey); + let header = Header::new(Algorithm::RS512); + let token = jwt_encode(&header, &data, &skey)?; + + Ok(token) +} + +pub fn rsa_gen() -> (Vec, Vec) { + let mut rng = rand::thread_rng(); + let bits = 2048; + let skey = RsaPrivateKey::new(&mut rng, bits).expect("RSA err"); + let pkey = RsaPublicKey::from(&skey); + + ( + skey.to_pkcs1_der().unwrap().as_bytes().to_vec(), + pkey.to_pkcs1_der().unwrap().as_bytes().to_vec() + ) +} + +pub async fn jwt_verify(pool: DBPool, token: &str) -> Result> { + let mut con = pool.get().await.unwrap(); + let mut ret = TokenData { header: Header::new(Algorithm::RS512), claims: json![{}] }; + + let q = con + .query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0")) + .await; + if q.is_err() { return Ok(ret) }; + let pkey = q.unwrap().pkey; + + let pkey = DecodingKey::from_rsa_der(&pkey); + let token = jwt_decode::(&token, &pkey, &Validation::new(Algorithm::RS512)); + + match token.is_ok() { + true => ret = token.clone().unwrap(), + _ => {} + } + + Ok(ret) +} + +pub async fn create_user(pool: DBPool, data: HashMap) -> Res { + let mut ret = true; + let mut con = pool.get().await.unwrap(); + + let login = &data.get("login").unwrap().trim().to_lowercase(); + let email = &data.get("email").unwrap().trim().to_lowercase(); + let pass = data.get("password").unwrap().trim(); + let pass2 = data.get("password2").unwrap().trim(); + + if pass != pass2 { ret = false }; + if pass.len() < 8 { ret = false }; + + let q = con + .query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login)) + .await; + if q.is_ok() { ret = false }; + + if ret { + let uuid = format!("{}", uuid_v4().as_hyphenated()); + let pass = bcrypt::hash(pass, 12).unwrap(); + + let q = con.query_parse::<()>(&query!( + r#"INSERT INTO bitauth.users { + uuid: ?, + login: ?, + password: ?, + email: ?, + tokens: [] + }"#, + uuid.clone(), + login.clone(), + pass, + email, + )).await; + if q.is_err() { ret = false } + + let q = con.query_parse::<()>(&query!( + r#"INSERT INTO bitauth.users_uuid { + login: ?, + uuid: ? + }"#, + login.clone(), + uuid.clone() + )).await; + if q.is_err() { ret = false } + } + + Ok(ret) +} + +pub async fn get_user(pool: DBPool, login: String) -> Res { + let mut con = pool.get().await.unwrap(); + + let q = con + .query_parse::(&query!("SELECT * FROM bitauth.users WHERE login = ?", login)) + .await; + + Ok(q?) +} diff --git a/src/main.rs b/src/main.rs index 8ac3ef0..cb6419f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,6 +86,7 @@ use { funcs::*, url::{ api, + login, }, }, }; @@ -273,8 +274,8 @@ async fn handle_connection(req: Request, pool: DBPool, ip: String) -> (body, parts.status, restype) = match req.uri().path().as_ref() { "/" => uri_index(), - "/cabinet" => uri_login(req, pool.clone(), &mut headers).await?, - "/login" => uri_login(req, pool.clone(), &mut headers).await?, + "/cabinet" => login::uri_login(req, pool.clone(), &mut headers).await?, + "/login" => login::uri_login(req, pool.clone(), &mut headers).await?, x if x == "/authorize" && logged => uri_authorize(req, pool.clone(), token).await?, "/authorize" => uri_auth_required(req, &mut headers).await?, "/register" => uri_register(req, pool.clone(), &mut headers).await?, @@ -291,18 +292,6 @@ async fn handle_connection(req: Request, pool: DBPool, ip: String) -> Ok(Response::from_parts(parts, Full::new(Bytes::from(body)))) } -fn set_cookie(headers: &mut HeaderMap, key: &str, value: &str) { - let time = DateTime::from_timestamp((time() + REFRESH_LIFETIME) as i64, 0) - .expect("REASON") - .to_rfc2822(); - let time = time.replace("+0000", "GMT"); - headers.append( - hyper::header::SET_COOKIE, - HeaderValue::from_str(format!("{}={}; HttpOnly; Expires={}", key, value, time).as_str()) - .unwrap() - ); -} - fn get_cookies(headers: HeaderMap) -> HashMap { let header = headers.get(hyper::header::COOKIE); let cookies = match header.is_none() { @@ -313,44 +302,6 @@ fn get_cookies(headers: HeaderMap) -> HashMap { double_split(cookies.to_owned(), ";", "=") } -fn set_location(headers: &mut HeaderMap, location: &str) { - headers.append( - hyper::header::LOCATION, - HeaderValue::from_str(format!("{}", location).as_str()).unwrap() - ); -} - -async fn uri_login(req: Request, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> { - let mut body = build_html(LOGIN_HTML); - let mut status = StatusCode::OK; - let restype: HeaderValue = "text/html".parse().unwrap(); - - if *req.method() == Method::POST { - let r = double_split(req.uri().query().or(Some("")).unwrap().to_owned(), "&", "="); - - let post = get_body_from_request(req).await?; - let post = String::from_utf8(post).unwrap(); - let post = double_split(post, "&", "="); - - let (access, refresh) = login_user(pool.clone(), post).await?; - - set_cookie(headers, "token", &access); - set_cookie(headers, "refresh", &refresh); - - let r = r.get("q"); - match r.is_some() { - true => { - status = StatusCode::FOUND; - body = "".to_owned(); - set_location(headers, format!("{}", url_decode(r.unwrap())?).as_str()); - }, - _ => {} - } - } - - Ok((body, status, restype)) -} - async fn uri_authorize(req: Request, pool: DBPool, token: String) -> Result<(String, StatusCode, HeaderValue)> { // TODO: Forward for versions. if *req.method() == Method::POST { @@ -469,244 +420,3 @@ fn uri_404() -> (String, StatusCode, HeaderValue) { let restype: HeaderValue = "text/html".parse().unwrap(); (build_html(NF_HTML), StatusCode::NOT_FOUND, restype) } - -async fn init_tables(pool: DBPool) -> Res<(), SkyError> { - let mut con = pool.get().await.unwrap(); - - let _ = con.query_parse::<()>(&query!("CREATE SPACE IF NOT EXISTS bitauth")).await; - let _ = con.query_parse::<()>(&query!(r#" - CREATE MODEL IF NOT EXISTS bitauth.users( - uuid: string, - login: string, - password: string, - email: string, - tokens: list {type: string} - ) - "#)).await; - let _ = con.query_parse::<()>(&query!(r#" - CREATE MODEL IF NOT EXISTS bitauth.users_uuid( - login: string, - uuid: string - ) - "#)).await; - let _ = con.query_parse::<()>(&query!(r#" - CREATE MODEL IF NOT EXISTS bitauth.sites( - uuid: string, - uid: string, - domain: string, - pkey: binary, - skey: binary - ) - "#)).await; - let _ = con.query_parse::<()>(&query!(r#" - CREATE MODEL IF NOT EXISTS bitauth.tokens( - uuid: string, - uid: string, - sid: string, - ref: string, - refend: uint32 - ) - "#)).await; - let _ = con.query_parse::<()>(&query!(r#" - CREATE MODEL IF NOT EXISTS bitauth.v0( - session: string, - login: string, - uuid: string, - expire: uint32 - ) - "#)).await; - - let q = con.query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = 0")).await; - if q.is_err() { - let (skey, pkey) = rsa_gen(); - let _ = con.query_parse::<()>(&query!( - "INSERT INTO bitauth.sites { uuid: ?, uid: ?, domain: ?, pkey: ?, skey: ? }", - "0", "0", "", pkey, skey - )).await; - }; - - Ok(()) -} - -async fn get_user(pool: DBPool, login: String) -> Res { - let mut con = pool.get().await.unwrap(); - - let q = con - .query_parse::(&query!("SELECT * FROM bitauth.users WHERE login = ?", login)) - .await; - - Ok(q?) -} - -async fn login_user(pool: DBPool, data: HashMap) -> Res<(String, String), SkyError> { - let mut ret: (String, String) = Default::default(); - let mut con = pool.get().await.unwrap(); - - let login = &data.get("login").unwrap().trim().to_lowercase(); - let pass = data.get("password").unwrap().trim(); - - let q = con - .query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login)) - .await; - if q.is_err() { println!("{:?}", q.err()); return Ok(ret); }; - let (uuid,) = q.unwrap(); - - let q = con - .query_parse::(&query!("SELECT * FROM bitauth.users WHERE uuid = ?", uuid.clone())) - .await; - if q.is_err() { - discord_wh_send( - format!("Пизда тут, ну тип, да, какая-то ебень с uuid, чел потерялся...\n`{}`\n**{}**", uuid, login) - ).await; - return Ok(ret); - } - - // TODO: Say to user about trouble. - - let q = q.unwrap(); - if bcrypt::verify(pass, q.password.as_str()).unwrap() { - let token = format!("{}", uuid_v4().as_hyphenated()); - let reftoken = format!("{}", uuid_v4().as_hyphenated()); - let time = time(); - let uuid: String = q.uuid; - let login: String = q.login; - - let _ = con.query_parse::<()>(&query!( - "INSERT INTO bitauth.tokens { uuid: ?, uid: ?, sid: ?, ref: ?, refend: ? }", - token.clone(), uuid.clone(), "0", reftoken.clone(), time + REFRESH_LIFETIME - )).await; - let _ = con.query_parse::<()>(&query!( - "UPDATE bitauth.users SET tokens += ? WHERE login = ?", - token.clone(), login.clone() - )).await; - - ret = ( - jwt_sign(pool.clone(), json!({ - "login": login.clone(), - "uuid": uuid.clone(), - "iat": time, - "exp": time + TOKEN_LIFETIME - })).await.unwrap(), - jwt_sign(pool.clone(), json!({ - "uuid": token.clone(), - "iat": time, - "ref": reftoken.clone(), - "exp": time + REFRESH_LIFETIME - })).await.unwrap() - ); - } - - Ok(ret) -} - -async fn create_user(pool: DBPool, data: HashMap) -> Res { - let mut ret = true; - let mut con = pool.get().await.unwrap(); - - let login = &data.get("login").unwrap().trim().to_lowercase(); - let email = &data.get("email").unwrap().trim().to_lowercase(); - let pass = data.get("password").unwrap().trim(); - let pass2 = data.get("password2").unwrap().trim(); - - if pass != pass2 { ret = false }; - if pass.len() < 8 { ret = false }; - - let q = con - .query_parse::<(String,)>(&query!("SELECT uuid FROM bitauth.users_uuid WHERE login = ?", login)) - .await; - if q.is_ok() { ret = false }; - - if ret { - let uuid = format!("{}", uuid_v4().as_hyphenated()); - let pass = bcrypt::hash(pass, 12).unwrap(); - - let q = con.query_parse::<()>(&query!( - r#"INSERT INTO bitauth.users { - uuid: ?, - login: ?, - password: ?, - email: ?, - tokens: [] - }"#, - uuid.clone(), - login.clone(), - pass, - email, - )).await; - if q.is_err() { ret = false } - - let q = con.query_parse::<()>(&query!( - r#"INSERT INTO bitauth.users_uuid { - login: ?, - uuid: ? - }"#, - login.clone(), - uuid.clone() - )).await; - if q.is_err() { ret = false } - } - - Ok(ret) -} - -async fn get_body_from_request(mut req: Request) -> Result> { - let mut body: Vec = vec![]; - while let Some(next) = req.frame().await { - let frame = next?; - if let Some(chunk) = frame.data_ref() { - body.extend_from_slice(chunk); - } - } - - Ok(body) -} - -fn rsa_gen() -> (Vec, Vec) { - let mut rng = rand::thread_rng(); - let bits = 2048; - let skey = RsaPrivateKey::new(&mut rng, bits).expect("RSA err"); - let pkey = RsaPublicKey::from(&skey); - - ( - skey.to_pkcs1_der().unwrap().as_bytes().to_vec(), - pkey.to_pkcs1_der().unwrap().as_bytes().to_vec() - ) -} - -async fn jwt_sign(pool: DBPool, data: Json) -> Result { - let mut con = pool.get().await.unwrap(); - - let q = con - .query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0")) - .await; - if q.is_err() { return Ok("".to_owned()) }; - let skey = q.unwrap().skey; - - let skey = EncodingKey::from_rsa_der(&skey); - let header = Header::new(Algorithm::RS512); - let token = jwt_encode(&header, &data, &skey)?; - - Ok(token) -} - -async fn jwt_verify(pool: DBPool, token: &str) -> Result> { - let mut con = pool.get().await.unwrap(); - let mut ret = TokenData { header: Header::new(Algorithm::RS512), claims: json![{}] }; - - let q = con - .query_parse::(&query!("SELECT * FROM bitauth.sites WHERE uuid = ?", "0")) - .await; - if q.is_err() { return Ok(ret) }; - let pkey = q.unwrap().pkey; - - let pkey = DecodingKey::from_rsa_der(&pkey); - let token = jwt_decode::(&token, &pkey, &Validation::new(Algorithm::RS512)); - - match token.is_ok() { - true => ret = token.clone().unwrap(), - _ => {} - } - - Ok(ret) -} - diff --git a/src/url.rs b/src/url.rs index e5fdf85..e649fd1 100644 --- a/src/url.rs +++ b/src/url.rs @@ -1 +1,2 @@ pub mod api; +pub mod login; diff --git a/src/url/login.rs b/src/url/login.rs new file mode 100644 index 0000000..1d214af --- /dev/null +++ b/src/url/login.rs @@ -0,0 +1,124 @@ +use { + std::{ + sync::Arc, + net::SocketAddr, + collections::HashMap, + process::exit, + }, + chrono::{ + DateTime, + }, + http_body_util::{ + Full, + BodyExt, + }, + hyper::{ + StatusCode, + Request, + Response, + Method, + HeaderMap, + header::HeaderValue, + body::{ + Bytes, + Incoming, + }, + server::conn::http1 as Server, + service::service_fn, + }, + hyper_util::{ + rt::TokioIo, + }, + tokio::{ + net::TcpListener, + }, + rsa::{ + pkcs1::{ + EncodeRsaPublicKey, + EncodeRsaPrivateKey, + }, + RsaPrivateKey, + RsaPublicKey, + }, + jsonwebtoken as jwt, + jwt::{ + Header, + Algorithm, + TokenData, + Validation, + EncodingKey, + DecodingKey, + encode as jwt_encode, + decode as jwt_decode, + }, + serde_json::{ + Value as Json, + Map as JsonMap, + json, + }, + skytable::{ + query, + Config, + pool::{ + self, + ConnectionMgrTcp + }, + error::Error as SkyError, + }, + bb8::{ + Pool, + }, + urlencoding::{ + encode as url_encode, + decode as url_decode, + }, + crate::{ + types::{ + users::Users, + sites::Sites, + }, + html::*, + funcs::*, + url::{ + api, + login, + }, + }, +}; + +type Res = std::result::Result; +type Result = std::result::Result>; +type DBPool = Arc>; +type FullBytes = Result>>; + + +pub async fn uri_login(req: Request, pool: DBPool, headers: &mut HeaderMap) -> Result<(String, StatusCode, HeaderValue)> { + let mut body = build_html(LOGIN_HTML); + let mut status = StatusCode::OK; + let restype: HeaderValue = "text/html".parse().unwrap(); + + if *req.method() == Method::POST { + let r = double_split(req.uri().query().or(Some("")).unwrap().to_owned(), "&", "="); + + let post = get_body_from_request(req).await?; + let post = String::from_utf8(post).unwrap(); + let post = double_split(post, "&", "="); + + let (access, refresh) = login_user(pool.clone(), post).await?; + + set_cookie(headers, "token", &access); + set_cookie(headers, "refresh", &refresh); + + let r = r.get("q"); + match r.is_some() { + true => { + status = StatusCode::FOUND; + body = "".to_owned(); + set_location(headers, format!("{}", url_decode(r.unwrap())?).as_str()); + }, + _ => {} + } + } + + Ok((body, status, restype)) +}