Add tokens routes
This commit is contained in:
		
							
								
								
									
										28
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -1598,6 +1598,9 @@ name = "ipnet"
 | 
			
		||||
version = "2.11.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "is_terminal_polyfill"
 | 
			
		||||
@@ -1660,6 +1663,29 @@ version = "0.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy-regex"
 | 
			
		||||
version = "3.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "lazy-regex-proc_macros",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "regex",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy-regex-proc_macros"
 | 
			
		||||
version = "3.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lazy_static"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
@@ -1826,6 +1852,8 @@ dependencies = [
 | 
			
		||||
 "diesel_migrations",
 | 
			
		||||
 "env_logger",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
 "ipnet",
 | 
			
		||||
 "lazy-regex",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "light-openid",
 | 
			
		||||
 "log",
 | 
			
		||||
 
 | 
			
		||||
@@ -24,3 +24,5 @@ futures-util = "0.3.31"
 | 
			
		||||
serde_json = "1.0.140"
 | 
			
		||||
light-openid = "1.0.4"
 | 
			
		||||
rand = "0.9.0"
 | 
			
		||||
ipnet = { version = "2.11.0", features = ["serde"] }
 | 
			
		||||
lazy-regex = "3.4.1"
 | 
			
		||||
@@ -10,20 +10,19 @@ CREATE TABLE users
 | 
			
		||||
CREATE TABLE token
 | 
			
		||||
(
 | 
			
		||||
    id               SERIAL PRIMARY KEY,
 | 
			
		||||
    label            VARCHAR(150) NOT NULL,
 | 
			
		||||
    name             VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_create      BIGINT       NOT NULL,
 | 
			
		||||
    time_update      BIGINT       NOT NULL,
 | 
			
		||||
    user_id          INTEGER      NOT NULL REFERENCES users ON DELETE CASCADE,
 | 
			
		||||
    token_value      VARCHAR(150) NOT NULL,
 | 
			
		||||
    time_used        BIGINT       NOT NULL,
 | 
			
		||||
    max_inactivity   INTEGER,
 | 
			
		||||
    ip_restriction   VARCHAR(50),
 | 
			
		||||
    max_inactivity   INTEGER      NOT NULL,
 | 
			
		||||
    ip_net           VARCHAR(50),
 | 
			
		||||
    read_only        BOOLEAN      NOT NULL DEFAULT true,
 | 
			
		||||
    right_account    BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_movement   BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_inbox      BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_attachment BOOLEAN      NOT NULL DEFAULT false,
 | 
			
		||||
    right_user       BOOLEAN      NOT NULL DEFAULT false
 | 
			
		||||
    right_auth       BOOLEAN      NOT NULL DEFAULT false
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE attachment
 | 
			
		||||
@@ -33,7 +32,7 @@ CREATE TABLE attachment
 | 
			
		||||
    mime_type   VARCHAR(150) NOT NULL,
 | 
			
		||||
    sha512      VARCHAR(130) NOT NULL,
 | 
			
		||||
    file_size   INTEGER      NOT NULL,
 | 
			
		||||
    user_id     INTEGER      NOT NULL REFERENCES users ON DELETE RESTRICT
 | 
			
		||||
    user_id     INTEGER      NOT NULL REFERENCES users ON DELETE SET NULL
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CREATE TABLE account
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,8 @@
 | 
			
		||||
//! # Project constants
 | 
			
		||||
 | 
			
		||||
/// Length of generated tokens
 | 
			
		||||
pub const TOKENS_LEN: usize = 50;
 | 
			
		||||
 | 
			
		||||
/// Header used to authenticate API requests made using a token
 | 
			
		||||
pub const API_TOKEN_HEADER: &str = "X-Auth-Token";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ use crate::app_config::AppConfig;
 | 
			
		||||
use crate::controllers::{HttpFailure, HttpResult};
 | 
			
		||||
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
 | 
			
		||||
use crate::extractors::money_session::MoneySession;
 | 
			
		||||
use crate::services::users_service;
 | 
			
		||||
use crate::services::{tokens_service, users_service};
 | 
			
		||||
use actix_remote_ip::RemoteIP;
 | 
			
		||||
use actix_web::{HttpResponse, web};
 | 
			
		||||
use light_openid::primitives::OpenIDConfig;
 | 
			
		||||
@@ -118,6 +118,10 @@ pub async fn sign_out(auth: AuthExtractor, session: MoneySession) -> HttpResult
 | 
			
		||||
            session.unset_current_user()?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthenticatedMethod::Token(token) => {
 | 
			
		||||
            tokens_service::delete(token.user_id(), token.id()).await?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthenticatedMethod::Dev => {
 | 
			
		||||
            // Nothing to be done, user is always authenticated
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ use std::error::Error;
 | 
			
		||||
 | 
			
		||||
pub mod auth_controller;
 | 
			
		||||
pub mod server_controller;
 | 
			
		||||
pub mod tokens_controller;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum HttpFailure {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,45 @@ pub async fn robots_txt() -> HttpResponse {
 | 
			
		||||
        .body("User-agent: *\nDisallow: /\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct LenConstraints {
 | 
			
		||||
    min: usize,
 | 
			
		||||
    max: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LenConstraints {
 | 
			
		||||
    pub fn new(min: usize, max: usize) -> Self {
 | 
			
		||||
        Self { min, max }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn not_empty(max: usize) -> Self {
 | 
			
		||||
        Self { min: 1, max }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn max_only(max: usize) -> Self {
 | 
			
		||||
        Self { min: 0, max }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check_str(&self, s: &str) -> bool {
 | 
			
		||||
        s.len() >= self.min && s.len() <= self.max
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check_u32(&self, v: u32) -> bool {
 | 
			
		||||
        v >= self.min as u32 && v <= self.max as u32
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct ServerConstraints {
 | 
			
		||||
    // TODO
 | 
			
		||||
    pub token_name: LenConstraints,
 | 
			
		||||
    pub token_ip_net: LenConstraints,
 | 
			
		||||
    pub token_max_inactivity: LenConstraints,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ServerConstraints {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            // TODO
 | 
			
		||||
            token_name: LenConstraints::new(5, 255),
 | 
			
		||||
            token_ip_net: LenConstraints::max_only(44),
 | 
			
		||||
            token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								moneymgr_backend/src/controllers/tokens_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								moneymgr_backend/src/controllers/tokens_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
use crate::controllers::HttpResult;
 | 
			
		||||
use crate::controllers::server_controller::ServerConstraints;
 | 
			
		||||
use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
 | 
			
		||||
use crate::models::tokens::{Token, TokenID};
 | 
			
		||||
use crate::services::tokens_service;
 | 
			
		||||
use crate::services::tokens_service::NewTokenInfo;
 | 
			
		||||
use actix_web::{HttpResponse, web};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct CreateTokenBody {
 | 
			
		||||
    name: String,
 | 
			
		||||
    ip_net: Option<ipnet::IpNet>,
 | 
			
		||||
    max_inactivity: u32,
 | 
			
		||||
    read_only: bool,
 | 
			
		||||
    right_account: bool,
 | 
			
		||||
    right_movement: bool,
 | 
			
		||||
    right_inbox: bool,
 | 
			
		||||
    right_attachment: bool,
 | 
			
		||||
    right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct CreateTokenResult {
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    info: Token,
 | 
			
		||||
    token: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new token
 | 
			
		||||
pub async fn create(auth: AuthExtractor, req: web::Json<CreateTokenBody>) -> HttpResult {
 | 
			
		||||
    if matches!(auth.method, AuthenticatedMethod::Token(_)) {
 | 
			
		||||
        return Ok(HttpResponse::Forbidden()
 | 
			
		||||
            .json("It is not allowed to create a token using another token!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let constraints = ServerConstraints::default();
 | 
			
		||||
 | 
			
		||||
    if !lazy_regex::regex!("^[a-zA-Z0-9 :-]+$").is_match(&req.name) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Token name contains invalid characters!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if !constraints.token_name.check_str(&req.name) {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid token name length!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if !constraints
 | 
			
		||||
        .token_max_inactivity
 | 
			
		||||
        .check_u32(req.max_inactivity)
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid token max inactivity!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let token = tokens_service::create(NewTokenInfo {
 | 
			
		||||
        user_id: auth.user_id(),
 | 
			
		||||
        max_inactivity: req.max_inactivity,
 | 
			
		||||
        ip_net: req.ip_net,
 | 
			
		||||
        name: req.name.clone(),
 | 
			
		||||
        read_only: req.read_only,
 | 
			
		||||
        right_account: req.right_account,
 | 
			
		||||
        right_movement: req.right_movement,
 | 
			
		||||
        right_inbox: req.right_inbox,
 | 
			
		||||
        right_attachment: req.right_attachment,
 | 
			
		||||
        right_auth: req.right_auth,
 | 
			
		||||
    })
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Created().json(CreateTokenResult {
 | 
			
		||||
        token: token.token_value.to_string(),
 | 
			
		||||
        info: token,
 | 
			
		||||
    }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the list of tokens of the user
 | 
			
		||||
pub async fn get_list(auth: AuthExtractor) -> HttpResult {
 | 
			
		||||
    Ok(HttpResponse::Ok().json(tokens_service::get_list_user(auth.user_id()).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct TokenIDInPath {
 | 
			
		||||
    id: TokenID,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Delete an API access token
 | 
			
		||||
pub async fn delete(auth: AuthExtractor, path: web::Path<TokenIDInPath>) -> HttpResult {
 | 
			
		||||
    tokens_service::delete(auth.user_id(), path.id).await?;
 | 
			
		||||
    Ok(HttpResponse::Accepted().finish())
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::extractors::money_session::MoneySession;
 | 
			
		||||
use crate::models::users::User;
 | 
			
		||||
use crate::models::tokens::Token;
 | 
			
		||||
use crate::models::users::{User, UserID};
 | 
			
		||||
use crate::services::users_service;
 | 
			
		||||
use actix_web::dev::Payload;
 | 
			
		||||
use actix_web::error::ErrorPreconditionFailed;
 | 
			
		||||
@@ -12,6 +13,8 @@ pub enum AuthenticatedMethod {
 | 
			
		||||
    Cookie,
 | 
			
		||||
    /// User is authenticated through command line, for debugging purposes only
 | 
			
		||||
    Dev,
 | 
			
		||||
    // TODO : token implementation
 | 
			
		||||
    Token(Token),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Authentication extractor. Extract authentication information from request
 | 
			
		||||
@@ -20,6 +23,13 @@ pub struct AuthExtractor {
 | 
			
		||||
    pub user: User,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AuthExtractor {
 | 
			
		||||
    /// Get current user ID
 | 
			
		||||
    pub fn user_id(&self) -> UserID {
 | 
			
		||||
        self.user.id()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromRequest for AuthExtractor {
 | 
			
		||||
    type Error = Error;
 | 
			
		||||
    type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ use actix_web::middleware::Logger;
 | 
			
		||||
use actix_web::{App, HttpServer, web};
 | 
			
		||||
use moneymgr_backend::app_config::AppConfig;
 | 
			
		||||
use moneymgr_backend::connections::{db_connection, s3_connection};
 | 
			
		||||
use moneymgr_backend::controllers::{auth_controller, server_controller};
 | 
			
		||||
use moneymgr_backend::controllers::*;
 | 
			
		||||
use moneymgr_backend::services::users_service;
 | 
			
		||||
use moneymgr_backend::{constants, routines};
 | 
			
		||||
 | 
			
		||||
@@ -90,6 +90,16 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/auth/sign_out",
 | 
			
		||||
                web::get().to(auth_controller::sign_out),
 | 
			
		||||
            )
 | 
			
		||||
            // Tokens controller
 | 
			
		||||
            .route("/api/tokens", web::post().to(tokens_controller::create))
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/tokens/list",
 | 
			
		||||
                web::get().to(tokens_controller::get_list),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/tokens/{id}",
 | 
			
		||||
                web::delete().to(tokens_controller::delete),
 | 
			
		||||
            )
 | 
			
		||||
    })
 | 
			
		||||
    .bind(AppConfig::get().listen_address.as_str())?
 | 
			
		||||
    .run()
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,2 @@
 | 
			
		||||
pub mod tokens;
 | 
			
		||||
pub mod users;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								moneymgr_backend/src/models/tokens.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								moneymgr_backend/src/models/tokens.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
use crate::models::users::UserID;
 | 
			
		||||
use crate::schema::*;
 | 
			
		||||
use crate::utils::time_utils::time;
 | 
			
		||||
use diesel::prelude::*;
 | 
			
		||||
use std::cmp::min;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
 | 
			
		||||
pub struct TokenID(pub i32);
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Queryable, Debug, Clone, serde::Serialize)]
 | 
			
		||||
pub struct Token {
 | 
			
		||||
    id: i32,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub time_create: i64,
 | 
			
		||||
    user_id: i32,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub token_value: String,
 | 
			
		||||
    pub time_used: i64,
 | 
			
		||||
    pub max_inactivity: i32,
 | 
			
		||||
    ip_net: Option<String>,
 | 
			
		||||
    pub read_only: bool,
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Token {
 | 
			
		||||
    pub fn id(&self) -> TokenID {
 | 
			
		||||
        TokenID(self.id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn user_id(&self) -> UserID {
 | 
			
		||||
        UserID(self.user_id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn ip_net(&self) -> Option<ipnet::IpNet> {
 | 
			
		||||
        self.ip_net
 | 
			
		||||
            .as_ref()
 | 
			
		||||
            .map(|i| ipnet::IpNet::from_str(i).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn shall_update_time_used(&self) -> bool {
 | 
			
		||||
        let refresh_interval = min(600, self.max_inactivity / 10);
 | 
			
		||||
 | 
			
		||||
        (self.time_used as u64) < time() - refresh_interval as u64
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_expired(&self) -> bool {
 | 
			
		||||
        (self.time_used + self.max_inactivity as i64) < time() as i64
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Insertable)]
 | 
			
		||||
#[diesel(table_name = token)]
 | 
			
		||||
pub struct NewToken<'a> {
 | 
			
		||||
    pub name: &'a str,
 | 
			
		||||
    pub user_id: i32,
 | 
			
		||||
    pub time_create: i64,
 | 
			
		||||
    pub time_used: i64,
 | 
			
		||||
    pub max_inactivity: i32,
 | 
			
		||||
    pub ip_net: Option<&'a str>,
 | 
			
		||||
    pub token_value: &'a str,
 | 
			
		||||
    pub read_only: bool,
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
@@ -19,6 +19,7 @@ pub async fn main_routine() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn exec_routine() -> anyhow::Result<()> {
 | 
			
		||||
    // TODO
 | 
			
		||||
    // TODO : remove orphan attachment
 | 
			
		||||
    // TODO : remove outdated tokens
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -55,22 +55,21 @@ diesel::table! {
 | 
			
		||||
    token (id) {
 | 
			
		||||
        id -> Int4,
 | 
			
		||||
        #[max_length = 150]
 | 
			
		||||
        label -> Varchar,
 | 
			
		||||
        name -> Varchar,
 | 
			
		||||
        time_create -> Int8,
 | 
			
		||||
        time_update -> Int8,
 | 
			
		||||
        user_id -> Int4,
 | 
			
		||||
        #[max_length = 150]
 | 
			
		||||
        token_value -> Varchar,
 | 
			
		||||
        time_used -> Int8,
 | 
			
		||||
        max_inactivity -> Nullable<Int4>,
 | 
			
		||||
        max_inactivity -> Int4,
 | 
			
		||||
        #[max_length = 50]
 | 
			
		||||
        ip_restriction -> Nullable<Varchar>,
 | 
			
		||||
        ip_net -> Nullable<Varchar>,
 | 
			
		||||
        read_only -> Bool,
 | 
			
		||||
        right_account -> Bool,
 | 
			
		||||
        right_movement -> Bool,
 | 
			
		||||
        right_inbox -> Bool,
 | 
			
		||||
        right_attachment -> Bool,
 | 
			
		||||
        right_user -> Bool,
 | 
			
		||||
        right_auth -> Bool,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,2 @@
 | 
			
		||||
pub mod tokens_service;
 | 
			
		||||
pub mod users_service;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										108
									
								
								moneymgr_backend/src/services/tokens_service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								moneymgr_backend/src/services/tokens_service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
use diesel::prelude::*;
 | 
			
		||||
 | 
			
		||||
use crate::connections::db_connection::db;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::models::tokens::{NewToken, Token, TokenID};
 | 
			
		||||
use crate::models::users::UserID;
 | 
			
		||||
use crate::schema::token;
 | 
			
		||||
use crate::utils::rand_utils::rand_string;
 | 
			
		||||
use crate::utils::time_utils::time;
 | 
			
		||||
 | 
			
		||||
pub struct NewTokenInfo {
 | 
			
		||||
    pub user_id: UserID,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub max_inactivity: u32,
 | 
			
		||||
    pub ip_net: Option<ipnet::IpNet>,
 | 
			
		||||
    pub read_only: bool,
 | 
			
		||||
    pub right_account: bool,
 | 
			
		||||
    pub right_movement: bool,
 | 
			
		||||
    pub right_inbox: bool,
 | 
			
		||||
    pub right_attachment: bool,
 | 
			
		||||
    pub right_auth: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new token
 | 
			
		||||
pub async fn create(new_token: NewTokenInfo) -> anyhow::Result<Token> {
 | 
			
		||||
    let ip_net = new_token.ip_net.map(|i| i.to_string());
 | 
			
		||||
    let token = rand_string(constants::TOKENS_LEN);
 | 
			
		||||
    let t = NewToken {
 | 
			
		||||
        name: &new_token.name,
 | 
			
		||||
        user_id: new_token.user_id.0,
 | 
			
		||||
        time_create: time() as i64,
 | 
			
		||||
        time_used: time() as i64,
 | 
			
		||||
        max_inactivity: new_token.max_inactivity as i32,
 | 
			
		||||
        read_only: new_token.read_only,
 | 
			
		||||
        ip_net: ip_net.as_deref(),
 | 
			
		||||
        token_value: &token,
 | 
			
		||||
        right_auth: new_token.right_auth,
 | 
			
		||||
        right_account: new_token.right_account,
 | 
			
		||||
        right_movement: new_token.right_movement,
 | 
			
		||||
        right_inbox: new_token.right_inbox,
 | 
			
		||||
        right_attachment: new_token.right_attachment,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let res = diesel::insert_into(token::table)
 | 
			
		||||
        .values(&t)
 | 
			
		||||
        .get_result(&mut db()?)?;
 | 
			
		||||
 | 
			
		||||
    Ok(res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get a single token by its id
 | 
			
		||||
pub async fn get_by_id(token_id: TokenID) -> anyhow::Result<Token> {
 | 
			
		||||
    Ok(token::table
 | 
			
		||||
        .filter(token::dsl::id.eq(token_id.0))
 | 
			
		||||
        .get_result(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get a single token by its name
 | 
			
		||||
pub fn get_by_name(name: &str) -> anyhow::Result<Token> {
 | 
			
		||||
    Ok(token::table
 | 
			
		||||
        .filter(token::dsl::name.eq(name))
 | 
			
		||||
        .get_result(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get a single token by its value
 | 
			
		||||
pub async fn get_by_value(value: &str) -> anyhow::Result<Token> {
 | 
			
		||||
    Ok(token::table
 | 
			
		||||
        .filter(token::dsl::token_value.eq(value))
 | 
			
		||||
        .get_result(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the token of a user
 | 
			
		||||
pub async fn get_list_user(id: UserID) -> anyhow::Result<Vec<Token>> {
 | 
			
		||||
    Ok(token::table
 | 
			
		||||
        .filter(token::dsl::user_id.eq(id.0))
 | 
			
		||||
        .get_results(&mut db()?)?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Update last used value of a token
 | 
			
		||||
pub async fn update_time_used(token: &Token) -> anyhow::Result<()> {
 | 
			
		||||
    diesel::update(token::dsl::token.filter(token::dsl::id.eq(token.id().0)))
 | 
			
		||||
        .set(token::dsl::time_used.eq(time() as i64))
 | 
			
		||||
        .execute(&mut db()?)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Delete the token of a user
 | 
			
		||||
pub async fn delete(user_id: UserID, token_id: TokenID) -> anyhow::Result<()> {
 | 
			
		||||
    diesel::delete(
 | 
			
		||||
        token::dsl::token.filter(
 | 
			
		||||
            token::dsl::id
 | 
			
		||||
                .eq(token_id.0)
 | 
			
		||||
                .and(token::dsl::user_id.eq(user_id.0)),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    .execute(&mut db()?)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Remove outdated token
 | 
			
		||||
pub async fn cleanup() -> anyhow::Result<()> {
 | 
			
		||||
    let query = format!(
 | 
			
		||||
        "DELETE from token where last_used + max_inactivity < {};",
 | 
			
		||||
        time()
 | 
			
		||||
    );
 | 
			
		||||
    diesel::sql_query(query).execute(&mut db()?)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user