Adds user authorization to the actix backend.

This commit is contained in:
2023-03-25 19:20:38 +01:00
parent 1c8110cdb0
commit 534e6867d8
11 changed files with 142 additions and 44 deletions

View File

@@ -5,7 +5,7 @@ use actix_web::HttpServer;
use actix_web::App;
use actix_web::web;
use diesel::PgConnection;
use request::{login, register};
use request::{login, register, gamenights};
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
@@ -28,6 +28,7 @@ async fn main() -> std::io::Result<()> {
.app_data(web::Data::new(pool.clone()))
.service(login)
.service(register)
.service(gamenights)
})
.bind(("::1", 8080))?
.run()

View File

@@ -0,0 +1,65 @@
use std::future::{Ready, ready};
use actix_web::{FromRequest, http, HttpRequest, dev::Payload, web::Data};
use chrono::Utc;
use jsonwebtoken::{encode, Header, EncodingKey, decode, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::{schema::user::{User, get_user}, DbPool};
use super::error::ApiError;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
exp: i64,
uid: Uuid
}
fn get_claims(req: &HttpRequest) -> Result<Claims, ApiError> {
let token = req.headers()
.get(http::header::AUTHORIZATION)
.map(|h| h.to_str().unwrap().split_at(7).1.to_string());
let token = token.ok_or(ApiError{
status: 400,
message: "JWT-token was not specified in the Authorization header as Bearer: token".to_string()
})?;
println!("{:?}", token);
let secret = "secret";
Ok(decode::<Claims>(token.as_str(), &DecodingKey::from_secret(secret.as_bytes()), &Validation::default())?.claims)
}
pub fn get_token(user: &User) -> Result<String, ApiError> {
let claims = Claims {
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
uid: user.id,
};
println!("{:?}", claims);
let secret = "secret";
Ok(encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()))?)
}
impl FromRequest for User {
type Error = ApiError;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
ready(
(|| -> Result<User, ApiError>{
let pool = req.app_data::<Data<DbPool>>().expect("No database configured");
let mut conn = pool.get().expect("couldn't get db connection from pool");
let uid = get_claims(req)?.uid;
Ok(get_user(&mut conn, uid)?)
})()
)
}
}

View File

@@ -0,0 +1,14 @@
use actix_web::{get, web, Responder, http::header::ContentType, HttpResponse};
use crate::{DbPool, request::{error::ApiError, responses::GameNightResponse}, schema::{self, user::User}};
#[get("/gamenights")]
pub async fn gamenights(pool: web::Data<DbPool>, _user: User) -> Result<impl Responder, ApiError> {
let mut conn = pool.get().expect("couldn't get db connection from pool");
let gamenights = schema::gamenights(&mut conn)?;
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&GameNightResponse { gamenights })?)
)
}

View File

@@ -1,8 +1,11 @@
mod requests;
mod responses;
mod handler;
mod user_handlers;
mod gamenight_handlers;
mod error;
mod authorization;
pub use handler::login;
pub use handler::register;
pub use user_handlers::login;
pub use user_handlers::register;
pub use gamenight_handlers::gamenights;

View File

@@ -1,6 +1,8 @@
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::schema::gamenight::Gamenight;
#[derive(Serialize, Deserialize)]
pub struct LoginResponse {
pub login_result: bool,
@@ -27,4 +29,9 @@ impl LoginResponse {
jwt_token: None
}
}
}
#[derive(Serialize, Deserialize)]
pub struct GameNightResponse {
pub gamenights: Vec::<Gamenight>
}

View File

@@ -1,18 +1,14 @@
use actix_web::http::header::ContentType;
use actix_web::{web, post, HttpResponse, Responder};
use chrono::Utc;
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use actix_web::{web, post, HttpResponse, Responder, get};
use validator::ValidateArgs;
use crate::DbPool;
use crate::request::requests::{Login, Register};
use crate::request::error::ApiError;
use crate::request::responses::LoginResponse;
use crate::schema::user::Role;
use crate::request::authorization::get_token;
use crate::schema::{self};
use serde_json;
use jsonwebtoken::{encode, EncodingKey, Header};
impl Into<schema::user::LoginUser> for Login {
fn into(self) -> schema::user::LoginUser {
@@ -33,14 +29,9 @@ impl Into<schema::user::Register> for Register {
}
}
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
exp: i64,
uid: Uuid,
role: Role,
}
#[post("/login")]
#[get("/token")]
pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Result<impl Responder, ApiError> {
let data = login_data.into_inner();
@@ -50,18 +41,7 @@ pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Res
})
.await??
{
let my_claims = Claims {
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
uid: user.id,
role: user.role,
};
let secret = "secret";
let token = encode(
&Header::default(),
&my_claims,
&EncodingKey::from_secret(secret.as_bytes()))?;
let token = get_token(&user)?;
LoginResponse::success(user.id, token)
}
else {
@@ -74,7 +54,7 @@ pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Res
)
}
#[post("/register")]
#[post("/user")]
pub async fn register(pool: web::Data<DbPool>, register_data: web::Json<Register>) -> Result<impl Responder, ApiError> {
let data1 = register_data.clone();
let data2 = register_data.clone();

View File

@@ -0,0 +1,20 @@
use chrono::{DateTime, Utc};
use diesel::{Insertable, Queryable, PgConnection, RunQueryDsl};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::schema::schema::gamenight;
use super::error::DatabaseError;
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
#[diesel(table_name = gamenight)]
pub struct Gamenight {
pub id: Uuid,
pub name: String,
pub datetime: DateTime<Utc>,
pub owner_id: Uuid,
}
pub fn gamenights(conn: &mut PgConnection, ) -> Result<Vec::<Gamenight>, DatabaseError> {
Ok(gamenight::table.load::<Gamenight>(conn)?)
}

View File

@@ -1,6 +1,8 @@
pub mod user;
pub mod error;
pub mod schema;
pub mod gamenight;
pub use user::login;
pub use user::register;
pub use gamenight::gamenights;

View File

@@ -85,6 +85,10 @@ pub fn login(conn: &mut PgConnection, user: LoginUser) -> Result<Option<User>, D
}
}
pub fn get_user(conn: &mut PgConnection, id: Uuid) -> Result<User, DatabaseError> {
Ok(users::table.find(id).first(conn)?)
}
pub fn unique_username(username: &String, conn: &mut PgConnection) -> Result<(), ValidationError> {
match users::table
.count()