use std::ops::Index; use axum::extract::{Path, Query, State}; use axum::{Json, Router}; use axum::response::{IntoResponse, NoContent, Response}; use axum::routing::post; use log::error; use serde::Deserialize; use sqlx::Error; use rand::{distributions::Alphanumeric, Rng}; use uuid::Uuid; use crate::storage::DbPool; enum TokenType { } #[derive(Debug)] pub struct IdentityDTO { id: String, name: String } #[derive(Debug)] pub struct TokenDTO { key: String, id: String, identity_id: Option, parent_id: Option, expiry: Option, } #[derive(Debug)] pub struct TokenRoleMembershipDTO { role_name: String, token_id: String, } #[derive(Deserialize)] struct RequestBodyPostLookup { token: String, } // TODO: Make string generation secure fn get_random_string(len: usize) -> String { rand::thread_rng() .sample_iter(&Alphanumeric) .take(len) .map(char::from) .collect() } // Returns if a token was created or not. Prints out the created token to the console. pub async fn create_root_token_if_none_exist(pool: &DbPool) -> bool { let exists = sqlx::query!( r#"SELECT service_token.* FROM service_token, service_token_role_membership WHERE service_token.id = service_token_role_membership.token_id AND service_token_role_membership.role_name = 'root' LIMIT 1"#).fetch_one(pool).await .is_ok(); if exists { return false; } let result = create_root_token(pool).await; if result.is_err() { let error = result.err().unwrap(); error!("create_root_token failed: {:?}", error); panic!("create_root_token failed: {:?}", error); } println!("\n\nYour root token is: {}", result.unwrap()); println!("It will only be displayed once!\n\n"); true } // Return the token key if successful async fn create_root_token(pool: &DbPool) -> Result { let id = Uuid::new_v4().to_string(); let key = "s.".to_string() + &get_random_string(24); let result = sqlx::query!(r#" INSERT INTO service_token (id, key) VALUES ($1, $2); INSERT INTO service_token_role_membership (token_id, role_name) VALUES ($3, 'root'); "#, id, key, id).execute(pool).await; if result.is_ok() { return Ok(key); } Err(result.unwrap_err()) } // Gets the current time in seconds since unix epoch fn get_time_as_int() -> i64 { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64 } fn get_token_type(token: &TokenDTO) -> Result { Ok(match token.key.clone().chars().next().unwrap_or('?') { 's' => "service", 'b' => "batch", 'r' => "recovery", _ => { error!("Unsupported token type"); return Err("Unsupported token type"); } }.to_string()) } pub async fn get_token_from_key(token_key: &str, pool: &DbPool) -> Result { let time = get_time_as_int(); sqlx::query_as!( TokenDTO, r#"SELECT * FROM 'service_token' WHERE key = $1 AND (expiry IS NULL OR expiry > $2) LIMIT 1"#, token_key, time).fetch_one(pool).await } pub async fn get_roles_from_token(token: &TokenDTO, pool:&DbPool) -> Vec { let result = sqlx::query_as!( TokenRoleMembershipDTO, r#"SELECT * FROM 'service_token_role_membership' WHERE token_id = $1"#, token.id).fetch_all(pool).await; result.unwrap_or(Vec::new()).iter().map(|r| r.role_name.to_string()).collect() } pub fn token_auth_router(pool: DbPool) -> Router { Router::new() .route("/lookup", post(post_lookup)) .with_state(pool) } async fn post_lookup( State(pool): State, Json(body): Json ) -> Result { let token = body.token; Ok(IntoResponse::into_response(token)) } async fn get_accessors() {} async fn post_create() {} async fn post_create_orphan() {} async fn post_create_role() {} async fn get_lookup() {} async fn get_lookup_self() {} async fn post_lookup_self() {} async fn post_renew() {} async fn post_renew_accessor() {} async fn post_renew_self() {} async fn post_revoke() {} async fn post_revoke_accessor() {} async fn post_revoke_orphan() {} async fn post_revoke_self() {} async fn get_roles() {} async fn get_role_by_name() {} async fn post_role_by_name() {} async fn delete_role_by_name() {} async fn post_tidy() {}