diff --git a/server-api/src/auth.rs b/server-api/src/auth.rs index 63fe2cd..a1f68bd 100644 --- a/server-api/src/auth.rs +++ b/server-api/src/auth.rs @@ -29,7 +29,7 @@ impl Claim { .checked_add_signed(Duration::weeks(1)) .expect("Couldn't generate an expirey date") .timestamp(), - cookie: generate_secret() + cookie: generate_cookie() } } } @@ -69,16 +69,25 @@ fn valid_perms(member: Member, path: &str) -> bool { } } +fn rng_secret(length: usize) -> String { + use getrandom::getrandom; + use base64::{encode_config, URL_SAFE}; + + let mut buf: Vec = vec![0;length]; + getrandom(&mut buf).unwrap(); + encode_config(buf,URL_SAFE) + +} + pub fn generate_secret() -> String { /* * Generates a url-safe-plaintext secret for our db * */ - use getrandom::getrandom; - use base64::{encode_config, URL_SAFE}; + return rng_secret(64); +} - let mut buf: Vec = vec![0;64]; - getrandom(&mut buf).unwrap(); - encode_config(buf,URL_SAFE) +pub fn generate_cookie() -> String { + return rng_secret(32) } pub fn encrypt_secret(raw: &str) -> BcryptResult { @@ -86,51 +95,37 @@ pub fn encrypt_secret(raw: &str) -> BcryptResult { return bcrypt::hash(raw, BCRYPT_COST); } -fn get_jwt_json(params: &serde_json::Value) -> Option<&str> { +fn jwt_from_serde(params: &serde_json::Value) -> Option<&str> { // gets the `token` from the parameters // option -> some(value) -> string return params.get("token")?.as_str(); } -async fn valid_jwt(token: &str) -> AuthReason { +async fn valid_jwt(p: &Pool, token: &str) -> AuthReason { use jsonwebtoken::{ decode, DecodingKey, Validation, Algorithm }; - // TODO: add a blacklist in redis to make sure we don't ever accidently authenticate a bad - // token - // NOTE: for now we're doing purely stateless validation with a bs key - // crypto things that should prolly not fail assuming we're configured correctly let algo = Algorithm::HS512; let dk = DecodingKey::from_base64_secret(&HMAC_SECRET).unwrap(); - let raw = decode::(token, &dk, &Validation::new(algo)); + if let Ok(decoded) = decode::(token, &dk, &Validation::new(algo)) { - // if the decoding worked then check on the redis cache for the jwt - // recall we have the id as a lookup but it is mapped to a session-id - // that mapping should be the same as the temporary usermapping - // Additionally: invalidating a session id is as easy as just making a new new for the user - if raw.is_err() { + // subject used for querying speed NOT security + let listed = db::auth::listed_jwt(p, decoded.claims.sub, token).await.unwrap(); + let active = Utc::now().timestamp() < decoded.claims.exp; + + return match listed && active { + true => AuthReason::Good, + false => AuthReason::BadKey + }; + } + else { return AuthReason::BadKey; } - let raw = raw.unwrap(); - let id = raw.claims.sub; - let sesh_id = raw.claims.cookie; - - return match db::auth::active_jwt(id, &sesh_id).await { - Ok(active) => { - match active { - true => AuthReason::Good, - false => AuthReason::BadKey - } - }, - Err(err) => { - AuthReason::ServerIssue(format!("{}", err)) - } - }; } -fn get_login(params: &serde_json::Value) -> Option<(db::UBigInt, &str)> { +fn login_params_from_serde(params: &serde_json::Value) -> Option<(db::UBigInt, &str)> { let id_v = params.get("id"); let secret_v = params.get("secret"); return match (id_v, secret_v) { @@ -153,7 +148,7 @@ pub async fn wall_entry<'path, 'pool, 'params>( // Dont need to auth if it's not required let open_path = routes::is_open(path); - let jwt = get_jwt_json(params); + let jwt = jwt_from_serde(params); if open_path { // ignore the parameters since they're irelevant return AuthReason::OpenAuth; @@ -161,9 +156,9 @@ pub async fn wall_entry<'path, 'pool, 'params>( if let Some(jwt) = jwt { // get the headers here - return valid_jwt(jwt).await; + return valid_jwt(pool, jwt).await; } - if let Some((id, secret)) = get_login(params) { + if let Some((id, secret)) = login_params_from_serde(params) { // Last chance we might be hitting the /login route so we have to do the heavy auth flow if path != routes::AUTH_LOGIN { @@ -188,7 +183,7 @@ pub async fn wall_entry<'path, 'pool, 'params>( return AuthReason::NoKey; } -pub async fn login_get_jwt(response: &mut hyper::Response, params: serde_json::Value) { +pub async fn login_get_jwt(p: &Pool, response: &mut hyper::Response, params: serde_json::Value) { // basically this route generates a jwt for the user and returns via the jwt key // in the json response use jsonwebtoken::{ @@ -199,24 +194,28 @@ pub async fn login_get_jwt(response: &mut hyper::Response, params: let id = params.get("id").unwrap().as_u64().unwrap(); // only route where we have the "id is there guarantee" let claim = Claim::new(id); let header = Header::new(Algorithm::HS512); + println!("{:?}-{:?}", header, claim); let encoded = encode( &header, &claim, &EncodingKey::from_base64_secret(HMAC_SECRET.as_ref()).expect("Couldn't encode from secret")) .expect("Could not encode JWT"); - if let Ok(_) = db::auth::add_jwt(id, &encoded).await { - response.headers_mut().insert("Content-Type", + match db::auth::add_jwt(p, encoded.as_ref()).await { + Ok(_) => { + response.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json")); - let payload = serde_json::json!({ - "jwt": encoded - }); - *response.body_mut() = hyper::Body::from(payload.to_string()); - } - else { - *response.status_mut() = hyper::StatusCode::INTERNAL_SERVER_ERROR; - } + let payload = serde_json::json!({ + "jwt": encoded + }); + *response.body_mut() = hyper::Body::from(payload.to_string()); + }, + Err(e) => { + eprintln!("{}", e); + *response.status_mut() = hyper::StatusCode::INTERNAL_SERVER_ERROR; + } + }; }