user: implement authorization guard
This commit is contained in:
parent
c82ed40251
commit
fea3fb235d
|
@ -11,7 +11,8 @@ pub fn routes() -> impl Into<Vec<rocket::Route>> {
|
||||||
auth,
|
auth,
|
||||||
auth_change_pw,
|
auth_change_pw,
|
||||||
auth_sign_in,
|
auth_sign_in,
|
||||||
auth_params
|
auth_params,
|
||||||
|
auth_ping
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,3 +121,9 @@ fn auth_change_pw(db: DbConn, params: Json<ChangePwParams>) -> Custom<JsonResp<(
|
||||||
error_resp(Status::InternalServerError, vec![e])
|
error_resp(Status::InternalServerError, vec![e])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For testing the User request guard
|
||||||
|
#[get("/auth/ping")]
|
||||||
|
fn auth_ping(_db: DbConn, u: user::User) -> Custom<JsonResp<String>> {
|
||||||
|
Custom(Status::Ok, Json(Response::Success(u.email)))
|
||||||
|
}
|
39
src/tests.rs
39
src/tests.rs
|
@ -1,6 +1,6 @@
|
||||||
use crate::build_rocket;
|
use crate::build_rocket;
|
||||||
use rocket::local::Client;
|
use rocket::local::Client;
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{Header, ContentType, Status};
|
||||||
use lazy_static::*;
|
use lazy_static::*;
|
||||||
|
|
||||||
fn get_test_client() -> Client {
|
fn get_test_client() -> Client {
|
||||||
|
@ -197,3 +197,40 @@ fn should_change_pw_successfully_and_log_in_successfully() {
|
||||||
.dispatch();
|
.dispatch();
|
||||||
assert_eq!(resp.status(), Status::Ok);
|
assert_eq!(resp.status(), Status::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_fail_authorize() {
|
||||||
|
let resp = CLIENT.get("/auth/ping").dispatch();
|
||||||
|
assert_eq!(resp.status(), Status::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_fail_authorize_2() {
|
||||||
|
let resp = CLIENT.get("/auth/ping")
|
||||||
|
.header(Header::new("Authorization", "iwoe0nvie0bv024ibv043bv"))
|
||||||
|
.dispatch();
|
||||||
|
assert_eq!(resp.status(), Status::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_success_authorize() {
|
||||||
|
let token = CLIENT.post("/auth")
|
||||||
|
.header(ContentType::JSON)
|
||||||
|
.body(r#"{
|
||||||
|
"email": "test7@example.com",
|
||||||
|
"password": "testpw",
|
||||||
|
"pw_cost": "100",
|
||||||
|
"pw_nonce": "whatever",
|
||||||
|
"version": "001"
|
||||||
|
}"#)
|
||||||
|
.dispatch()
|
||||||
|
.body_string()
|
||||||
|
.unwrap()
|
||||||
|
.replace("{\"token\":\"", "")
|
||||||
|
.replace("\"}", "");
|
||||||
|
let mut resp = CLIENT.get("/auth/ping")
|
||||||
|
.header(Header::new("Authorization", token))
|
||||||
|
.dispatch();
|
||||||
|
assert_eq!(resp.status(), Status::Ok);
|
||||||
|
assert_eq!(resp.body_string().unwrap(), "\"test7@example.com\"");
|
||||||
|
}
|
44
src/user.rs
44
src/user.rs
|
@ -3,10 +3,17 @@ use crate::schema::users::dsl::*;
|
||||||
use crate::{lock_db_write, lock_db_read};
|
use crate::{lock_db_write, lock_db_read};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
use rocket::request;
|
||||||
|
use rocket::http::Status;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref JWT_SECRET: String = env::var("SFRS_JWT_SECRET")
|
||||||
|
.expect("Please have SFRS_JWT_SECRET set");
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserOpError(pub String);
|
pub struct UserOpError(pub String);
|
||||||
|
|
||||||
|
@ -133,6 +140,18 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_user_by_token(db: &SqliteConnection, token: &str) -> Result<User, UserOpError> {
|
||||||
|
let parsed: jwt::Token<jwt::Claims, jwt::Registered> =
|
||||||
|
jwt::Token::parse(token).map_err(|_| "Invalid JWT Token".into())?;
|
||||||
|
if !parsed.verify(JWT_SECRET.as_bytes(), crypto::sha2::Sha256::new()) {
|
||||||
|
Err("JWT Signature Verification Error".into())
|
||||||
|
} else {
|
||||||
|
parsed.claims.sub
|
||||||
|
.ok_or("Malformed Token".into())
|
||||||
|
.and_then(|mail| Self::find_user_by_email(db, &mail))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a JWT token for the current user if password matches
|
// Create a JWT token for the current user if password matches
|
||||||
pub fn create_token(&self, passwd: &str) -> Result<String, UserOpError> {
|
pub fn create_token(&self, passwd: &str) -> Result<String, UserOpError> {
|
||||||
if self.password != passwd {
|
if self.password != passwd {
|
||||||
|
@ -150,9 +169,7 @@ impl User {
|
||||||
nbf: None,
|
nbf: None,
|
||||||
jti: None
|
jti: None
|
||||||
})
|
})
|
||||||
).signed(env::var("SFRS_JWT_SECRET")
|
).signed(JWT_SECRET.as_bytes(), crypto::sha2::Sha256::new())
|
||||||
.expect("Please have SFRS_JWT_SECRET set")
|
|
||||||
.as_bytes(), crypto::sha2::Sha256::new())
|
|
||||||
.map_err(|_| UserOpError::new("Failed to generate token"))
|
.map_err(|_| UserOpError::new("Failed to generate token"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,3 +192,24 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement request guard for User type
|
||||||
|
// This is intended for protecting authorized endpoints
|
||||||
|
impl<'a, 'r> request::FromRequest<'a, 'r> for User {
|
||||||
|
type Error = UserOpError;
|
||||||
|
|
||||||
|
fn from_request(request: &'a request::Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let token = request.headers().get_one("authorization");
|
||||||
|
match token {
|
||||||
|
None => request::Outcome::Failure((Status::Unauthorized, "Token missing".into())),
|
||||||
|
Some(token) => {
|
||||||
|
let result = Self::find_user_by_token(
|
||||||
|
&request.guard::<crate::DbConn>().unwrap(), token);
|
||||||
|
match result {
|
||||||
|
Ok(u) => request::Outcome::Success(u),
|
||||||
|
Err(err) => request::Outcome::Failure((Status::Unauthorized, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue