Compare commits
No commits in common. "4828b7e907e74ae88c126404b18f393aa926e713" and "c8c6d4cf0ac607fb6c3514ddbdaa37741bae1572" have entirely different histories.
4828b7e907
...
c8c6d4cf0a
13 changed files with 24 additions and 218 deletions
|
@ -1,19 +0,0 @@
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
meta {
|
|
||||||
name: Verify User
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{api_base}}/auth/login
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"username": "hure",
|
|
||||||
"password": "nüttchen"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,7 +25,6 @@ sea-orm = { version = "1.1", features = [
|
||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
uuid = "1"
|
|
||||||
|
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
HttpResponse, Responder, post,
|
HttpResponse, get, post,
|
||||||
web::{self, ServiceConfig},
|
web::{self, ServiceConfig},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{Database, error::ApiError};
|
use crate::error::ApiError;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct LoginRequest {
|
struct LoginRequest {
|
||||||
|
@ -19,17 +19,7 @@ pub fn setup(cfg: &mut ServiceConfig) {
|
||||||
|
|
||||||
#[post("/login")]
|
#[post("/login")]
|
||||||
async fn login(
|
async fn login(
|
||||||
db: web::Data<Database>,
|
|
||||||
login_request: web::Json<LoginRequest>,
|
login_request: web::Json<LoginRequest>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<impl Responder, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let login_request = login_request.into_inner();
|
|
||||||
|
|
||||||
let user_id = db
|
|
||||||
.verify_local_user(&login_request.username, &login_request.password)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
session.insert("user", user_id)?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
use std::path;
|
||||||
|
|
||||||
use actix_web::{Result, delete, get, post, put, web};
|
use actix_web::{Result, delete, get, post, put, web};
|
||||||
use uuid::Uuid;
|
use sea_orm::prelude::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::db::Database;
|
use crate::db::Database;
|
||||||
use crate::db::project::CreateProject;
|
use crate::db::project::CreateProject;
|
||||||
use crate::entity;
|
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
|
|
||||||
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{Database, entity, error::ApiError};
|
use actix_web::{Responder, delete, get, post, put};
|
||||||
use actix_web::{Responder, delete, get, post, put, web};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use validator::Validate;
|
|
||||||
|
|
||||||
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||||
cfg.service(get_users)
|
cfg.service(get_users)
|
||||||
|
@ -10,13 +7,6 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
|
||||||
.service(delete_user);
|
.service(delete_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
|
||||||
struct CreateUser {
|
|
||||||
username: String,
|
|
||||||
name: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
async fn get_users() -> impl Responder {
|
async fn get_users() -> impl Responder {
|
||||||
""
|
""
|
||||||
|
@ -28,16 +18,8 @@ async fn get_user() -> impl Responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("")]
|
#[post("")]
|
||||||
async fn create_user(
|
async fn create_user() -> impl Responder {
|
||||||
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("")]
|
#[put("")]
|
||||||
|
|
|
@ -9,25 +9,9 @@ pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
pub password_change_required: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[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 {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
@ -9,24 +9,16 @@ pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
#[sea_orm(unique)]
|
#[sea_orm(unique)]
|
||||||
pub username: String,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub role: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
#[sea_orm(has_one = "super::local_auth::Entity")]
|
|
||||||
LocalAuth,
|
|
||||||
#[sea_orm(has_many = "super::user_group_project::Entity")]
|
#[sea_orm(has_many = "super::user_group_project::Entity")]
|
||||||
UserGroupProject,
|
UserGroupProject,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Related<super::local_auth::Entity> for Entity {
|
|
||||||
fn to() -> RelationDef {
|
|
||||||
Relation::LocalAuth.def()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Related<super::user_group_project::Entity> for Entity {
|
impl Related<super::user_group_project::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::UserGroupProject.def()
|
Relation::UserGroupProject.def()
|
||||||
|
|
|
@ -2,11 +2,11 @@ use super::Database;
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::entity::project;
|
use entity::project;
|
||||||
use sea_orm::ActiveValue::{NotSet, Set, Unchanged};
|
use sea_orm::ActiveValue::{NotSet, Set, Unchanged};
|
||||||
|
use sea_orm::prelude::Uuid;
|
||||||
use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait};
|
use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use uuid::Uuid;
|
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
#[derive(Deserialize, Validate)]
|
||||||
|
|
|
@ -1,88 +1,11 @@
|
||||||
use crate::error::ApiError;
|
use super::Database;
|
||||||
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,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{Database, entity};
|
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub async fn create_user(
|
async fn create_user() {}
|
||||||
&self,
|
|
||||||
name: String,
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
) -> Result<entity::user::Model, ApiError> {
|
|
||||||
let argon2 = Argon2::default();
|
|
||||||
|
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
async fn verify_local_user() {}
|
||||||
let hash = argon2
|
|
||||||
.hash_password(password.as_bytes(), &salt)
|
|
||||||
.map_err(|err| ApiError::Argon2Error(err.to_string()))?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let user = self
|
async fn verify_ldap_user() {}
|
||||||
.conn
|
|
||||||
.transaction::<_, entity::user::Model, DbErr>(|txn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let user = entity::user::ActiveModel {
|
|
||||||
id: NotSet,
|
|
||||||
name: Set(name),
|
|
||||||
username: Set(username),
|
|
||||||
};
|
|
||||||
|
|
||||||
let user: entity::user::Model = user.insert(txn).await?;
|
async fn change_user_password() {}
|
||||||
|
|
||||||
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(user.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn verify_ldap_user() {}
|
|
||||||
|
|
||||||
pub async fn change_user_password() {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use actix_web::{HttpResponse, ResponseError, cookie::time::error, http::StatusCode};
|
use actix_web::{HttpResponse, ResponseError, cookie::time::error, http::StatusCode};
|
||||||
use sea_orm::TransactionError;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -14,10 +13,6 @@ pub enum ApiError {
|
||||||
BadRequest(String),
|
BadRequest(String),
|
||||||
#[error("Validation Error: {0}")]
|
#[error("Validation Error: {0}")]
|
||||||
ValidationError(#[from] validator::ValidationErrors),
|
ValidationError(#[from] validator::ValidationErrors),
|
||||||
#[error("Argon2 Error: {0}")]
|
|
||||||
Argon2Error(String),
|
|
||||||
#[error("Session insert error: {0}")]
|
|
||||||
SessionInsertError(#[from] actix_session::SessionInsertError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for ApiError {
|
impl ResponseError for ApiError {
|
||||||
|
@ -28,8 +23,6 @@ impl ResponseError for ApiError {
|
||||||
ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
|
ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
ApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
|
ApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
|
||||||
ApiError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
ApiError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||||
ApiError::Argon2Error(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
ApiError::SessionInsertError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +30,3 @@ impl ResponseError for ApiError {
|
||||||
HttpResponse::build(self.status_code()).body(self.to_string())
|
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,13 +1,14 @@
|
||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_session::{SessionMiddleware, storage::RedisSessionStore};
|
use actix_session::{SessionMiddleware, storage::RedisSessionStore};
|
||||||
use actix_web::{App, HttpResponse, HttpServer, cookie::Key, middleware::Logger, web};
|
use actix_web::{App, HttpResponse, HttpServer, cookie::Key, middleware::Logger, web};
|
||||||
|
use argon2::Argon2;
|
||||||
|
use db::Database;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
mod controller;
|
mod controller;
|
||||||
mod db;
|
mod db;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
pub use db::Database;
|
|
||||||
pub use db::entity;
|
pub use db::entity;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -35,6 +36,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let app = App::new()
|
let app = App::new()
|
||||||
.app_data(web::Data::new(database.clone()))
|
.app_data(web::Data::new(database.clone()))
|
||||||
|
.app_data(web::Data::new(Argon2::default()))
|
||||||
.app_data(web::Data::new(app_config.clone()))
|
.app_data(web::Data::new(app_config.clone()))
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(SessionMiddleware::new(
|
.wrap(SessionMiddleware::new(
|
||||||
|
|
|
@ -46,8 +46,8 @@ impl MigrationTrait for Migration {
|
||||||
.table(User::Table)
|
.table(User::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(pk_uuid(User::Id).extra("DEFAULT gen_random_uuid()"))
|
.col(pk_uuid(User::Id).extra("DEFAULT gen_random_uuid()"))
|
||||||
.col(string_uniq(User::Username))
|
.col(string_uniq(User::Name))
|
||||||
.col(string(User::Name))
|
.col(string(User::Role))
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -101,15 +101,6 @@ impl MigrationTrait for Migration {
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(pk_uuid(LocalAuth::Id))
|
.col(pk_uuid(LocalAuth::Id))
|
||||||
.col(string(LocalAuth::Hash))
|
.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(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -132,10 +123,6 @@ impl MigrationTrait for Migration {
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(UserGroupProject::Table).to_owned())
|
.drop_table(Table::drop().table(UserGroupProject::Table).to_owned())
|
||||||
.await?;
|
|
||||||
|
|
||||||
manager
|
|
||||||
.drop_table(Table::drop().table(LocalAuth::Table).to_owned())
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,8 +146,8 @@ enum Group {
|
||||||
enum User {
|
enum User {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
Username,
|
|
||||||
Name,
|
Name,
|
||||||
|
Role,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
|
@ -176,5 +163,4 @@ enum LocalAuth {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
Hash,
|
Hash,
|
||||||
PasswordChangeRequired,
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue