+ Jwt tables - SEE NOTE
! wat - because have to do maintain permissions on a per request level we have to do this check for permissions at what is basically every level, this does mean we have to hit the database for a lot of routes however there is a check that requests go through in order to avoid hitting the database whenever possible + rng field in claims now has real purpose It's purpose is to act as a validator field in the jwt table. By verifying rng fields we no longer have to store whole jwt's
This commit is contained in:
parent
9a22713080
commit
c850d42ce1
19
json-api/db/src/jwt.rs
Normal file
19
json-api/db/src/jwt.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use mysql_async::{Pool, params, Result, prelude::Queryable};
|
||||
|
||||
pub async fn listed(p: &Pool, id: u64, given_rng_value: &str) -> Result<bool> {
|
||||
let mut conn = p.get_conn().await?;
|
||||
let q = "SELECT rng FROM jwt WHERE id = :id";
|
||||
let row: Option<String> = conn.exec_first(q, params!{"id" => id}).await?;
|
||||
if let Some(value) = row {
|
||||
Ok(value == given_rng_value)
|
||||
} else{
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert(p: &Pool, id: u64, given_rng_value: &str) -> Result<()> {
|
||||
let mut conn = p.get_conn().await?;
|
||||
let q = "INSERT INTO jwt (id, rng) VALUES (:id, :rng)";
|
||||
conn.exec_drop(q, params!{"id" => id, "rng" => given_rng_value}).await?;
|
||||
Ok(())
|
||||
}
|
@ -6,6 +6,7 @@ pub mod invites;
|
||||
pub mod channels;
|
||||
pub mod messages;
|
||||
pub mod neighbors;
|
||||
pub mod jwt;
|
||||
|
||||
use std::vec::Vec;
|
||||
|
||||
|
1
json-api/migrations/2021-05-07-201858_jwt/down.sql
Normal file
1
json-api/migrations/2021-05-07-201858_jwt/down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE `jwt`;
|
5
json-api/migrations/2021-05-07-201858_jwt/up.sql
Normal file
5
json-api/migrations/2021-05-07-201858_jwt/up.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS `jwt` (
|
||||
`id` BIGINT UNSIGNED NOT NULL,
|
||||
`rng` VARCHAR(48) NOT NULL,
|
||||
PRIMARY KEY(`id`)
|
||||
);
|
@ -6,7 +6,6 @@ use std::collections::HashMap;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::routes;
|
||||
use crate::qs_param;
|
||||
|
||||
use db::{Response, Member};
|
||||
|
||||
@ -26,15 +25,16 @@ lazy_static! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claim {
|
||||
sub: db::UBigInt, // user id
|
||||
exp: db::BigInt, // expiry date
|
||||
nbf: i64,
|
||||
cookie: String, // unique cookie value
|
||||
pub struct Claim {
|
||||
sub: u64,// user id
|
||||
exp: i64, // expiry date
|
||||
nbf: i64, // "valid-start" time for the claim
|
||||
prm: u64, // user permissions
|
||||
rng: String, // Cheesy way of helping reduce the amount
|
||||
}
|
||||
|
||||
impl Claim {
|
||||
pub fn new(id: db::UBigInt) -> Claim {
|
||||
pub fn new(id: db::UBigInt, prm: u64) -> Claim {
|
||||
|
||||
// JWT's expire every 48 hours
|
||||
let now = SystemTime::now();
|
||||
@ -52,20 +52,20 @@ impl Claim {
|
||||
sub: id,
|
||||
exp,
|
||||
nbf,
|
||||
cookie: generate_cookie()
|
||||
prm,
|
||||
rng: generate_cookie()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// used when we create a new users for the first time
|
||||
#[derive(Debug)]
|
||||
pub enum AuthReason {
|
||||
Good, //passed regular check
|
||||
Good(Claim), //passed regular check
|
||||
OpenAuth, // route does not require auth
|
||||
NoKey, // key missing
|
||||
BadKey, // key is bad
|
||||
LoginValid, // used only to access the login route which is also our refresh
|
||||
LoginValid(Member), // used only to access the login route which is also our refresh
|
||||
ServerIssue(String) // for well 500's
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ fn valid_secret(given_pass: &str, hash: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_perms(member: Member, path: &str) -> bool {
|
||||
fn valid_perms(member: &Member, path: &str) -> bool {
|
||||
use crate::perms;
|
||||
// if there are perms on the current path make sure the user has them
|
||||
if let Some(p) = perms::get_perm_mask(path) {
|
||||
@ -120,7 +120,13 @@ pub fn encrypt_secret(raw: &str) -> BcryptResult<String> {
|
||||
}
|
||||
|
||||
|
||||
async fn valid_jwt(token: &str) -> AuthReason {
|
||||
async fn valid_jwt(pool: &Pool, path: &str, token: &str) -> AuthReason {
|
||||
/*
|
||||
* This function does a database check because of the requirement of always
|
||||
* enforcing permissions
|
||||
* TODO: Not all routes actually require a permissions check so we should
|
||||
* try to only do that DB call when absolutely required
|
||||
*/
|
||||
use jsonwebtoken::{
|
||||
decode, DecodingKey,
|
||||
Validation, Algorithm
|
||||
@ -136,7 +142,18 @@ async fn valid_jwt(token: &str) -> AuthReason {
|
||||
|
||||
let active = now < decoded.claims.exp;
|
||||
if active {
|
||||
AuthReason::Good
|
||||
if routes::requires_perms(path) {
|
||||
match db::jwt::listed(pool, decoded.claims.sub, decoded.claims.rng.as_str()).await {
|
||||
Ok(listed) => if listed {
|
||||
AuthReason::Good(decoded.claims)
|
||||
} else {
|
||||
AuthReason::BadKey
|
||||
}
|
||||
_ => AuthReason::BadKey
|
||||
}
|
||||
} else {
|
||||
AuthReason::Good(decoded.claims)
|
||||
}
|
||||
} else {
|
||||
AuthReason::BadKey
|
||||
}
|
||||
@ -182,7 +199,7 @@ 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, path, jwt).await;
|
||||
}
|
||||
if let Some((id, secret)) = login_params_from_qs(params) {
|
||||
// Last chance we might be hitting the /login route so we have to do the heavy auth flow
|
||||
@ -194,8 +211,8 @@ pub async fn wall_entry<'path, 'pool, 'params>(
|
||||
match Member::get(pool, id).await {
|
||||
Ok(response) => match response {
|
||||
Response::Row(user) => {
|
||||
if valid_secret(secret, &user.secret) && valid_perms(user, path){
|
||||
AuthReason::LoginValid
|
||||
if valid_secret(secret, &user.secret) && valid_perms(&user, path){
|
||||
AuthReason::LoginValid(user)
|
||||
}
|
||||
else {
|
||||
AuthReason::BadKey
|
||||
@ -215,7 +232,7 @@ pub async fn wall_entry<'path, 'pool, 'params>(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login_get_jwt(response: &mut hyper::Response<hyper::Body>, params: HashMap<String, String>) {
|
||||
pub async fn login_get_jwt(response: &mut hyper::Response<hyper::Body>, user: Member) {
|
||||
// Login data has already been validated at this point
|
||||
// Required data such as 'id' and 'secret' are there and validated
|
||||
use jsonwebtoken::{
|
||||
@ -225,10 +242,9 @@ pub async fn login_get_jwt(response: &mut hyper::Response<hyper::Body>, params:
|
||||
use hyper::header::HeaderValue;
|
||||
use crate::http;
|
||||
|
||||
let id = qs_param!(params, "id", u64).unwrap();
|
||||
let id = user.id;
|
||||
|
||||
|
||||
let claim = Claim::new(id);
|
||||
let claim = Claim::new(id, user.permissions);
|
||||
let header = Header::new(Algorithm::HS512);
|
||||
let encoded = encode(
|
||||
&header,
|
||||
@ -259,7 +275,7 @@ mod auth_tests {
|
||||
|
||||
#[test]
|
||||
fn verify_jwt() {
|
||||
let claim = super::Claim::new(123); // example claim that we send out
|
||||
let claim = super::Claim::new(123, 456); // example claim that we send out
|
||||
let header = Header::new(Algorithm::HS512); // header that basically all clients get
|
||||
let encoded = encode(
|
||||
&header,
|
||||
|
@ -24,7 +24,16 @@ pub const SELF_UPDATE_NICKNAME: Rstr= "/members/me/nickname";
|
||||
pub const SET_PERMS_BY_ADMIN: Rstr = "/admin/setpermisions"; // @requires perms::ADMIN
|
||||
pub const SET_NEW_ADMIN: Rstr = "/owner/newadmin"; // @requiers: owner perms
|
||||
|
||||
// Server -> Server Routes
|
||||
pub const GET_NEIGHBORS: Rstr = "/neighbors/list"; // @requires: none @note must be a member for this list
|
||||
|
||||
pub fn is_open(path: &str) -> bool {
|
||||
return path.starts_with("/join") || path.starts_with("/meta");
|
||||
}
|
||||
|
||||
pub fn requires_perms(path: &str) -> bool {
|
||||
return match path {
|
||||
AUTH_LOGIN | META | CHANNELS_LIST | GET_MYSELF | GET_NEIGHBORS => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user