may not actually really kind of works

This commit is contained in:
nora 2021-07-16 15:05:38 +02:00
parent 1a8f223d39
commit c2973477d1
8 changed files with 335 additions and 37 deletions

1
.env
View file

@ -1 +1,2 @@
DATABASE_URL=postgres://postgres:hugo123@localhost/karldbauth DATABASE_URL=postgres://postgres:hugo123@localhost/karldbauth
JWT_SECRET=halloichbineinsecretundsosehrsicherlmao34567

180
Cargo.lock generated
View file

@ -333,19 +333,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alcoholic_jwt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d27226e01414833a0443a67862f5aaf1cf3536b39b11f8b7d30d0e31b29d38db"
dependencies = [
"base64 0.10.1",
"openssl",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.50" version = "0.1.50"
@ -422,6 +409,18 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -448,6 +447,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "bumpalo"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -1176,6 +1181,29 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonwebtoken"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32"
dependencies = [
"base64 0.12.3",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]] [[package]]
name = "karlauth" name = "karlauth"
version = "0.1.0" version = "0.1.0"
@ -1184,12 +1212,12 @@ dependencies = [
"actix-service", "actix-service",
"actix-web", "actix-web",
"actix-web-httpauth", "actix-web-httpauth",
"alcoholic_jwt",
"chrono", "chrono",
"derive_more", "derive_more",
"diesel", "diesel",
"dotenv", "dotenv",
"futures 0.3.15", "futures 0.3.15",
"jsonwebtoken",
"r2d2", "r2d2",
"reqwest", "reqwest",
"serde", "serde",
@ -1397,6 +1425,17 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.44"
@ -1525,6 +1564,17 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
"base64 0.13.0",
"once_cell",
"regex",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "1.0.1" version = "1.0.1"
@ -1963,6 +2013,21 @@ dependencies = [
"quick-error", "quick-error",
] ]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.20" version = "0.1.20"
@ -2144,6 +2209,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simple_asn1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint",
"num-traits",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.3" version = "0.4.3"
@ -2176,6 +2252,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "string" name = "string"
version = "0.2.1" version = "0.2.1"
@ -2568,6 +2650,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "1.7.2" version = "1.7.2"
@ -2641,6 +2729,70 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
[[package]]
name = "web-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.3" version = "0.4.3"

View file

@ -19,6 +19,6 @@ serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
actix-service = "1.0.1" actix-service = "1.0.1"
alcoholic_jwt = "1.0.0" jsonwebtoken = "7.2.0"
reqwest = "0.9.22" reqwest = "0.9.22"
actix-rt = "1.0.0" actix-rt = "1.0.0"

View file

@ -8,17 +8,16 @@ use diesel::{delete, insert_into};
type DbResult<T> = Result<T, diesel::result::Error>; type DbResult<T> = Result<T, diesel::result::Error>;
pub fn get_all_users(db: &Pool) -> DbResult<User> { pub fn get_all_users(db: &Pool) -> DbResult<Vec<User>> {
use super::schema::users::dsl::*; use super::schema::users::dsl::*;
let conn = db.get().unwrap(); let conn = db.get().unwrap();
let items = users.load::<User>(&conn)?; users.load::<User>(&conn)
Ok(items)
} }
pub fn get_user_by_id(db: &Pool, id: i32) -> DbResult<User> { pub fn get_user_by_id(db: &Pool, user_id: i32) -> DbResult<User> {
use super::schema::users::dsl::*; use super::schema::users::dsl::*;
let conn = db.get().unwrap(); let conn = db.get().unwrap();
users.find(id).get_result::<User>(&conn) users.find(user_id).get_result::<User>(&conn)
} }
pub fn add_user(db: &Pool, user: InputUser) -> DbResult<User> { pub fn add_user(db: &Pool, user: InputUser) -> DbResult<User> {
@ -33,8 +32,8 @@ pub fn add_user(db: &Pool, user: InputUser) -> DbResult<User> {
insert_into(users).values(&new_user).get_result(&conn) insert_into(users).values(&new_user).get_result(&conn)
} }
pub fn delete_user(db: &Pool, id: i32) -> DbResult<usize> { pub fn delete_user(db: &Pool, user_id: i32) -> DbResult<usize> {
use super::schema::users::dsl::*; use super::schema::users::dsl::*;
let conn = db.get().unwrap(); let conn = db.get().unwrap();
delete(users.find(id)).execute(&conn) delete(users.find(user_id)).execute(&conn)
} }

82
src/auth.rs Normal file
View file

@ -0,0 +1,82 @@
use crate::errors::ServiceError;
use crate::models::User;
use actix_web::dev::{Payload, ServiceRequest};
use actix_web_httpauth::extractors::bearer::{BearerAuth, Config};
use actix_web_httpauth::extractors::AuthenticationError;
use chrono::Utc;
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Role {
None,
ReadAll,
WriteAll,
Admin,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Claims {
exp: usize,
uid: i32,
role: Role,
}
pub async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
) -> Result<ServiceRequest, actix_web::Error> {
let config = req
.app_data::<Config>()
.map(|data| data.get_ref().clone())
.unwrap_or(Default::default());
match validate_token(credentials.token()) {
Ok(claims) => {
//req.extensions_mut().insert(claims);
Ok(req)
}
Err(err) => Err(AuthenticationError::from(config).into()),
}
}
fn validate_token(token: &str) -> Result<Claims, ServiceError> {
let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET env var");
let decoded = jsonwebtoken::decode::<Claims>(
&token,
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::new(Algorithm::HS512),
)
.map_err(|_| ServiceError::JWTokenError)?
.claims;
if decoded.exp > Utc::now().timestamp() as usize {
Err(ServiceError::TokenExpiredError)
} else {
Ok(decoded)
}
}
pub fn create_jwt(user: &User) -> Result<String, ServiceError> {
let expiration = Utc::now()
.checked_add_signed(chrono::Duration::weeks(10))
.expect("valid timestamp")
.timestamp();
let claims = Claims {
exp: expiration as usize,
uid: user.id,
role: Role::ReadAll,
};
let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET env var");
let header = Header::new(Algorithm::HS512);
jsonwebtoken::encode(
&header,
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.map_err(|_| ServiceError::JWTCreationError)
}

View file

@ -0,0 +1,41 @@
use actix_web::{error::ResponseError, HttpResponse};
use derive_more::Display;
#[derive(Debug, Display)]
pub enum ServiceError {
#[display(fmt = "Internal Server Error")]
InternalServerError,
#[display(fmt = "BadRequest: {}", _0)]
BadRequest(String),
#[display(fmt = "JWT Creation Error")]
JWTCreationError,
#[display(fmt = "JWT Error")]
JWTokenError,
#[display(fmt = "No Permission Error")]
NoPermissionError,
#[display(fmt = "Token Expired Error")]
TokenExpiredError,
}
// impl ResponseError trait allows to convert our errors into http responses with appropriate data
impl ResponseError for ServiceError {
fn error_response(&self) -> HttpResponse {
match self {
ServiceError::InternalServerError => {
HttpResponse::InternalServerError().json("Internal Server Error, Please try later")
}
ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
ServiceError::JWTCreationError => {
HttpResponse::InternalServerError().json("Could not fetch JWKS")
}
ServiceError::JWTokenError => HttpResponse::BadRequest().json("Invalid JWT"),
ServiceError::NoPermissionError => HttpResponse::Unauthorized().json("No permissions"),
ServiceError::TokenExpiredError => HttpResponse::Unauthorized().json("Token expired"),
}
}
}

View file

@ -1,10 +1,9 @@
use super::actions; use super::actions;
use super::models::{NewUser, User};
use super::Pool; use super::Pool;
use actix_web::{web, HttpResponse, Responder}; use crate::auth::create_jwt;
use diesel::dsl::{delete, insert_into}; use crate::models::User;
use actix_web::{web, HttpResponse};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::vec::Vec;
type HttpResult = Result<HttpResponse, actix_web::Error>; type HttpResult = Result<HttpResponse, actix_web::Error>;
@ -15,13 +14,21 @@ pub struct InputUser {
pub email: String, pub email: String,
} }
#[derive(Debug, Serialize, Deserialize)]
struct UserWithToken {
pub user: User,
pub token: String,
}
/// handler for `GET /users`
pub async fn get_users(db: web::Data<Pool>) -> HttpResult { pub async fn get_users(db: web::Data<Pool>) -> HttpResult {
Ok(web::block(move || actions::get_all_users(&db)) Ok(web::block(move || actions::get_all_users(&db))
.await .await
.map(|user| user.into()) .map(|users| HttpResponse::Ok().json(users))
.map_err(|_| HttpResponse::InternalServerError())?) .map_err(|_| HttpResponse::InternalServerError())?)
} }
/// handler for `GET /users/{id}`
pub async fn get_user_by_id(db: web::Data<Pool>, user_id: web::Path<i32>) -> HttpResult { pub async fn get_user_by_id(db: web::Data<Pool>, user_id: web::Path<i32>) -> HttpResult {
Ok(web::block(move || actions::get_user_by_id(&db, *user_id)) Ok(web::block(move || actions::get_user_by_id(&db, *user_id))
.await .await
@ -29,15 +36,23 @@ pub async fn get_user_by_id(db: web::Data<Pool>, user_id: web::Path<i32>) -> Htt
.map_err(|_| HttpResponse::InternalServerError())?) .map_err(|_| HttpResponse::InternalServerError())?)
} }
/// handler for `POST /users`
pub async fn add_user(db: web::Data<Pool>, item: web::Json<InputUser>) -> HttpResult { pub async fn add_user(db: web::Data<Pool>, item: web::Json<InputUser>) -> HttpResult {
Ok(web::block(move || actions::add_user(&db, *item)) Ok(
web::block(move || actions::add_user(&db, item.into_inner()))
.await .await
.map(|user| user.into()) .map_err(|_| HttpResponse::InternalServerError())
.map_err(|_| HttpResponse::InternalServerError())?) .map(|user| {
HttpResponse::Ok().json(UserWithToken {
token: create_jwt(&user).expect("Could not create JWT"),
user,
})
})?,
)
} }
/// handler for `DELETE /users/{id}` /// handler for `DELETE /users/{id}`
pub async fn delete_user(db: web::Data<Pool>, user_id: web::Path<i32>) -> impl Responder { pub async fn delete_user(db: web::Data<Pool>, user_id: web::Path<i32>) -> HttpResult {
Ok(web::block(move || actions::delete_user(&db, *user_id)) Ok(web::block(move || actions::delete_user(&db, *user_id))
.await .await
.map(|count| HttpResponse::Ok().body(format!("Deleted {} user.", count))) .map(|count| HttpResponse::Ok().body(format!("Deleted {} user.", count)))

View file

@ -1,11 +1,14 @@
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
use actix_web::{dev::ServiceRequest, web, App, Error, HttpServer}; use crate::auth::validator;
use actix_web::{web, App, Error, HttpServer};
use actix_web_httpauth::middleware::HttpAuthentication;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager}; use diesel::r2d2::{self, ConnectionManager};
mod actions; mod actions;
mod auth;
mod errors; mod errors;
mod handlers; mod handlers;
mod models; mod models;
@ -25,12 +28,17 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to create pool."); .expect("Failed to create pool.");
HttpServer::new(move || { HttpServer::new(move || {
let auth_middleware = HttpAuthentication::bearer(validator);
App::new() App::new()
.data(pool.clone()) .data(pool.clone())
.route("/users", web::get().to(handlers::get_users))
.route("/users/{id}", web::get().to(handlers::get_user_by_id))
.route("/users", web::post().to(handlers::add_user)) .route("/users", web::post().to(handlers::add_user))
.route("/users/{id}", web::delete().to(handlers::delete_user)) .service(
web::scope("/users")
.wrap(auth_middleware)
.route("", web::get().to(handlers::get_users))
.route("/{id}", web::get().to(handlers::get_user_by_id))
.route("/{id}", web::delete().to(handlers::delete_user)),
)
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()