WIP
This commit is contained in:
parent
c8c6d4cf0a
commit
34d979da86
11 changed files with 186 additions and 19 deletions
19
bruno/user/Create user.bru
Normal file
19
bruno/user/Create user.bru
Normal file
|
@ -0,0 +1,19 @@
|
|||
meta {
|
||||
name: Create user
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{api_base}}/user
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"username": "hure",
|
||||
"name": "Dumme Nutte",
|
||||
"password": "nüttchen"
|
||||
}
|
||||
}
|
|
@ -22,4 +22,7 @@ async fn login(
|
|||
login_request: web::Json<LoginRequest>,
|
||||
session: Session,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let login_request = login_request.into_inner();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use std::path;
|
||||
|
||||
use actix_web::{Result, delete, get, post, put, web};
|
||||
use sea_orm::prelude::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::db::Database;
|
||||
use crate::db::project::CreateProject;
|
||||
use crate::entity;
|
||||
use crate::error::ApiError;
|
||||
|
||||
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use actix_web::{Responder, delete, get, post, put};
|
||||
use crate::{Database, entity, error::ApiError};
|
||||
use actix_web::{Responder, delete, get, post, put, web};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_users)
|
||||
|
@ -7,6 +10,13 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
|||
.service(delete_user);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
struct CreateUser {
|
||||
username: String,
|
||||
name: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[get("")]
|
||||
async fn get_users() -> impl Responder {
|
||||
""
|
||||
|
@ -18,8 +28,16 @@ async fn get_user() -> impl Responder {
|
|||
}
|
||||
|
||||
#[post("")]
|
||||
async fn create_user() -> impl Responder {
|
||||
""
|
||||
async fn create_user(
|
||||
db: web::Data<Database>,
|
||||
user: web::Json<CreateUser>,
|
||||
) -> Result<web::Json<entity::user::Model>, ApiError> {
|
||||
let user = user.into_inner();
|
||||
let result = db
|
||||
.create_user(user.name, user.username, user.password)
|
||||
.await?;
|
||||
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
#[put("")]
|
||||
|
|
|
@ -9,9 +9,25 @@ pub struct Model {
|
|||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub hash: String,
|
||||
pub password_change_required: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::Id",
|
||||
to = "super::user::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
|
@ -9,16 +9,24 @@ pub struct Model {
|
|||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
#[sea_orm(unique)]
|
||||
pub username: String,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_one = "super::local_auth::Entity")]
|
||||
LocalAuth,
|
||||
#[sea_orm(has_many = "super::user_group_project::Entity")]
|
||||
UserGroupProject,
|
||||
}
|
||||
|
||||
impl Related<super::local_auth::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::LocalAuth.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user_group_project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::UserGroupProject.def()
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::Database;
|
|||
use crate::error::ApiError;
|
||||
use log::debug;
|
||||
|
||||
use entity::project;
|
||||
use crate::entity::project;
|
||||
use sea_orm::ActiveValue::{NotSet, Set, Unchanged};
|
||||
use sea_orm::prelude::Uuid;
|
||||
use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait};
|
||||
|
|
|
@ -1,11 +1,90 @@
|
|||
use super::Database;
|
||||
use crate::error::ApiError;
|
||||
use argon2::{
|
||||
Argon2, PasswordHash, PasswordVerifier,
|
||||
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait,
|
||||
ActiveValue::{NotSet, Set},
|
||||
ColumnTrait, DbErr, EntityTrait, ModelTrait, QueryFilter, TransactionTrait,
|
||||
prelude::Uuid,
|
||||
};
|
||||
|
||||
use crate::{Database, entity};
|
||||
|
||||
use super::entity::local_auth;
|
||||
|
||||
impl Database {
|
||||
async fn create_user() {}
|
||||
pub async fn create_user(
|
||||
&self,
|
||||
name: String,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<entity::user::Model, ApiError> {
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
async fn verify_local_user() {}
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|err| ApiError::Argon2Error(err.to_string()))?
|
||||
.to_string();
|
||||
|
||||
async fn verify_ldap_user() {}
|
||||
let user = self
|
||||
.conn
|
||||
.transaction::<_, entity::user::Model, DbErr>(|txn| {
|
||||
Box::pin(async move {
|
||||
let user = entity::user::ActiveModel {
|
||||
id: NotSet,
|
||||
name: Set(name),
|
||||
username: Set(username),
|
||||
};
|
||||
|
||||
async fn change_user_password() {}
|
||||
let user: entity::user::Model = user.insert(txn).await?;
|
||||
|
||||
let local_auth = entity::local_auth::ActiveModel {
|
||||
id: Set(user.id),
|
||||
hash: Set(hash),
|
||||
password_change_required: NotSet,
|
||||
};
|
||||
|
||||
local_auth.insert(txn).await?;
|
||||
Ok(user)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn verify_local_user(
|
||||
&self,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<Uuid, ApiError> {
|
||||
let user = entity::user::Entity::find()
|
||||
.filter(entity::user::Column::Username.eq(username))
|
||||
.one(&self.conn)
|
||||
.await?
|
||||
.ok_or(ApiError::Unauthorized)?;
|
||||
|
||||
let local_auth = user
|
||||
.find_related(entity::local_auth::Entity)
|
||||
.one(&self.conn)
|
||||
.await?
|
||||
.ok_or(ApiError::Unauthorized)?;
|
||||
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
let password_hash = PasswordHash::new(&local_auth.hash)
|
||||
.map_err(|err| ApiError::Argon2Error(err.to_string()))?;
|
||||
|
||||
if let Err(_) = argon2.verify_password(password.as_bytes(), &password_hash) {
|
||||
return Err(ApiError::Unauthorized);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn verify_ldap_user() {}
|
||||
|
||||
pub async fn change_user_password() {}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use actix_web::{HttpResponse, ResponseError, cookie::time::error, http::StatusCode};
|
||||
use sea_orm::TransactionError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -13,6 +14,8 @@ pub enum ApiError {
|
|||
BadRequest(String),
|
||||
#[error("Validation Error: {0}")]
|
||||
ValidationError(#[from] validator::ValidationErrors),
|
||||
#[error("Argon2 Error: {0}")]
|
||||
Argon2Error(String),
|
||||
}
|
||||
|
||||
impl ResponseError for ApiError {
|
||||
|
@ -23,6 +26,7 @@ impl ResponseError for ApiError {
|
|||
ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||
ApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
|
||||
ApiError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||
ApiError::Argon2Error(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,3 +34,12 @@ impl ResponseError for ApiError {
|
|||
HttpResponse::build(self.status_code()).body(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionError<sea_orm::DbErr>> for ApiError {
|
||||
fn from(value: TransactionError<sea_orm::DbErr>) -> Self {
|
||||
Self::Database(match value {
|
||||
TransactionError::Connection(e) => e,
|
||||
TransactionError::Transaction(e) => e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use actix_files::NamedFile;
|
||||
use actix_session::{SessionMiddleware, storage::RedisSessionStore};
|
||||
use actix_web::{App, HttpResponse, HttpServer, cookie::Key, middleware::Logger, web};
|
||||
use argon2::Argon2;
|
||||
use db::Database;
|
||||
use log::debug;
|
||||
|
||||
mod controller;
|
||||
mod db;
|
||||
mod error;
|
||||
|
||||
pub use db::Database;
|
||||
pub use db::entity;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -36,7 +35,6 @@ async fn main() -> std::io::Result<()> {
|
|||
HttpServer::new(move || {
|
||||
let app = App::new()
|
||||
.app_data(web::Data::new(database.clone()))
|
||||
.app_data(web::Data::new(Argon2::default()))
|
||||
.app_data(web::Data::new(app_config.clone()))
|
||||
.wrap(Logger::default())
|
||||
.wrap(SessionMiddleware::new(
|
||||
|
|
|
@ -46,8 +46,8 @@ impl MigrationTrait for Migration {
|
|||
.table(User::Table)
|
||||
.if_not_exists()
|
||||
.col(pk_uuid(User::Id).extra("DEFAULT gen_random_uuid()"))
|
||||
.col(string_uniq(User::Name))
|
||||
.col(string(User::Role))
|
||||
.col(string_uniq(User::Username))
|
||||
.col(string(User::Name))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
@ -101,6 +101,15 @@ impl MigrationTrait for Migration {
|
|||
.if_not_exists()
|
||||
.col(pk_uuid(LocalAuth::Id))
|
||||
.col(string(LocalAuth::Hash))
|
||||
.col(boolean(LocalAuth::PasswordChangeRequired).default(true))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-localauth-user")
|
||||
.from(LocalAuth::Table, LocalAuth::Id)
|
||||
.to(User::Table, User::Id)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
|
@ -123,6 +132,10 @@ impl MigrationTrait for Migration {
|
|||
|
||||
manager
|
||||
.drop_table(Table::drop().table(UserGroupProject::Table).to_owned())
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(LocalAuth::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -146,8 +159,8 @@ enum Group {
|
|||
enum User {
|
||||
Table,
|
||||
Id,
|
||||
Username,
|
||||
Name,
|
||||
Role,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
|
@ -163,4 +176,5 @@ enum LocalAuth {
|
|||
Table,
|
||||
Id,
|
||||
Hash,
|
||||
PasswordChangeRequired,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue