Initial commit.
This commit is contained in:
Generated
+2627
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "lumiere-exposition"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "surveillant_systeme_exposition"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
lumiere-domain = { path = "../domain" }
|
||||
lumiere-application = { path = "../application" }
|
||||
|
||||
actix-web = { version = "4.13.0", features = ["rustls-0_23"] }
|
||||
actix-web-httpauth = "0.8.2"
|
||||
chrono = "0.4.44"
|
||||
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
uuid = { version = "1.23.0", features = ["v4", "serde"] }
|
||||
@@ -0,0 +1,36 @@
|
||||
extern crate core;
|
||||
|
||||
use crate::user::handler::{create_new_user, delete_by_id, get_all_users, initialise, login, update_user, update_user_password};
|
||||
use crate::user::validator::validator;
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
|
||||
pub mod user;
|
||||
|
||||
#[get("/health")]
|
||||
async fn health() -> impl Responder {
|
||||
"Ok!"
|
||||
}
|
||||
|
||||
pub fn init(cfg: &mut ServiceConfig) {
|
||||
cfg.service(health)
|
||||
.service(health)
|
||||
.service(initialise)
|
||||
.service(login)
|
||||
.service(
|
||||
web::scope("")
|
||||
.wrap(HttpAuthentication::bearer(validator))
|
||||
.service(create_new_user)
|
||||
.service(update_user)
|
||||
.service(update_user_password)
|
||||
.service(get_all_users)
|
||||
.service(delete_by_id)
|
||||
);
|
||||
|
||||
cfg.default_service(web::to(not_found));
|
||||
}
|
||||
|
||||
async fn not_found() -> impl Responder {
|
||||
HttpResponse::NotFound().finish()
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use actix_web::{delete, get, post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder};
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
use surveillant_systeme_application::user::service::UserService;
|
||||
use surveillant_systeme_domain::user::model::Claims;
|
||||
use crate::user::model::request::{CreateFirstUserRequestDto, CreateUserRequestDto, LoginRequestDto, UpdateUserPasswordRequestDto, UpdateUserRequestDto};
|
||||
use crate::user::model::response::{LoginResponseDto, UserDto};
|
||||
|
||||
#[post("/api/login")]
|
||||
pub async fn login(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
login_request: web::Json<LoginRequestDto>,
|
||||
) -> impl Responder {
|
||||
let login_result = user_service.login(
|
||||
&login_request.username.to_string(),
|
||||
&login_request.password.to_string(),
|
||||
);
|
||||
|
||||
match login_result {
|
||||
Ok(login_response) => HttpResponse::Ok().json(LoginResponseDto::new(&login_response)),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/users")]
|
||||
pub async fn create_new_user(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
create_user_request: web::Json<CreateUserRequestDto>,
|
||||
) -> impl Responder {
|
||||
let user_creation_result = user_service.create_user(
|
||||
&create_user_request.username.to_string(),
|
||||
&create_user_request.password.to_string(),
|
||||
);
|
||||
|
||||
match user_creation_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/api/users/{user_id}")]
|
||||
pub async fn update_user(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
update_user_request: web::Json<UpdateUserRequestDto>
|
||||
) -> impl Responder {
|
||||
let user_uuid = user_id.into_inner();
|
||||
let user_update_result = user_service.update_user(
|
||||
user_uuid,
|
||||
update_user_request.username.clone()
|
||||
);
|
||||
|
||||
match user_update_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/api/users/{user_id}/password")]
|
||||
pub async fn update_user_password(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
update_user_password_request: web::Json<UpdateUserPasswordRequestDto>
|
||||
) -> impl Responder {
|
||||
let user_uuid = user_id.into_inner();
|
||||
let user_password_update_result = user_service.update_user_password(
|
||||
user_uuid,
|
||||
update_user_password_request.password.clone()
|
||||
);
|
||||
|
||||
match user_password_update_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/initialise")]
|
||||
pub async fn initialise(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
request: web::Json<CreateFirstUserRequestDto>,
|
||||
) -> impl Responder {
|
||||
let initialisation_result = user_service.initialise(
|
||||
&request.initialisation_code,
|
||||
&request.username,
|
||||
&request.password,
|
||||
);
|
||||
match initialisation_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => HttpResponse::BadRequest().body(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/users")]
|
||||
pub async fn get_all_users(user_service: web::Data<Arc<UserService>>) -> impl Responder {
|
||||
match user_service.get_all_users() {
|
||||
Ok(users) => {
|
||||
let user_dto_list = users.iter().map(UserDto::new).collect::<Vec<UserDto>>();
|
||||
HttpResponse::Ok().json(user_dto_list)
|
||||
}
|
||||
Err(_) => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/api/users/{user_id}")]
|
||||
pub async fn delete_by_id(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
req: HttpRequest,
|
||||
) -> impl Responder {
|
||||
let authenticated_user_id = get_authenticated_user_id(req);
|
||||
match user_service.delete_by_id(&authenticated_user_id, &user_id) {
|
||||
Ok(()) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_authenticated_user_id(req: HttpRequest) -> Uuid {
|
||||
req.extensions()
|
||||
.get::<Claims>()
|
||||
.map(|claims| &claims.sub)
|
||||
.map(|authenticated_user_id_as_str| authenticated_user_id_as_str.as_str())
|
||||
.map(Uuid::parse_str)
|
||||
.unwrap()
|
||||
.expect("Unable to retrieve the authenticated user id…")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod handler;
|
||||
mod model;
|
||||
pub mod validator;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,31 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateFirstUserRequestDto {
|
||||
pub initialisation_code: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateUserRequestDto {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserRequestDto {
|
||||
pub username: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserPasswordRequestDto {
|
||||
pub password: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginRequestDto {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use surveillant_systeme_domain::user::model::{LoginResponse, RefreshToken, User};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UserDto {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl UserDto {
|
||||
pub fn new(user: &User) -> UserDto {
|
||||
Self {
|
||||
id: user.id,
|
||||
username: user.username.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginResponseDto {
|
||||
pub token_type: String,
|
||||
pub access_token: String,
|
||||
pub refresh_token: RefreshTokenDto,
|
||||
}
|
||||
|
||||
impl LoginResponseDto {
|
||||
pub fn new(login_response: &LoginResponse) -> Self {
|
||||
Self {
|
||||
token_type: login_response.token_type.clone(),
|
||||
access_token: login_response.access_token.clone(),
|
||||
refresh_token: RefreshTokenDto::new(&login_response.refresh_token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefreshTokenDto {
|
||||
pub user_id: Uuid,
|
||||
pub value: String,
|
||||
pub expiration_date: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RefreshTokenDto {
|
||||
pub fn new(refresh_token: &RefreshToken) -> Self {
|
||||
Self {
|
||||
user_id: refresh_token.user_id.clone(),
|
||||
value: refresh_token.value.clone(),
|
||||
expiration_date: refresh_token.expiration_date.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::env;
|
||||
use actix_web::{dev, HttpMessage};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use surveillant_systeme_domain::user::model::Claims;
|
||||
|
||||
fn get_jwt_secret() -> String {
|
||||
env::var("JWT_SECRET").expect("JWT_SECRET must be set.")
|
||||
}
|
||||
|
||||
pub async fn validator(
|
||||
request: dev::ServiceRequest,
|
||||
credentials: BearerAuth
|
||||
) -> Result<dev::ServiceRequest, (actix_web::Error, dev::ServiceRequest)> {
|
||||
let jwt_secret = get_jwt_secret();
|
||||
let token = credentials.token();
|
||||
|
||||
match decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
||||
&Validation::new(Algorithm::HS256)
|
||||
) {
|
||||
Ok(decoded) => {
|
||||
request.extensions_mut().insert(decoded.claims);
|
||||
Ok(request)
|
||||
}
|
||||
Err(_) => {
|
||||
Err((
|
||||
actix_web::error::ErrorUnauthorized("The provided JWT is invalid or expired."),
|
||||
request
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user