Skip to content

Commit

Permalink
[server] Add admin user #813 (#822)
Browse files Browse the repository at this point in the history
add admin user
  • Loading branch information
michaelvlach authored Dec 2, 2023
1 parent e550f29 commit b8364a1
Show file tree
Hide file tree
Showing 16 changed files with 265 additions and 95 deletions.
27 changes: 21 additions & 6 deletions agdb_server/openapi/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
"200": {
"description": "Server is shutting down"
}
}
},
"security": [
{
"Token": []
}
]
}
},
"/api/v1/db/add": {
Expand Down Expand Up @@ -146,6 +151,19 @@
]
}
},
"/api/v1/status": {
"get": {
"tags": [
"crate::routes"
],
"operationId": "status",
"responses": {
"200": {
"description": "Server is ready"
}
}
}
},
"/api/v1/user/change_password": {
"post": {
"tags": [
Expand Down Expand Up @@ -235,9 +253,9 @@
"200": {
"description": "Login successful",
"content": {
"application/json": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/UserToken"
"type": "string"
}
}
}
Expand Down Expand Up @@ -321,9 +339,6 @@
"type": "string"
}
}
},
"UserToken": {
"type": "string"
}
},
"securitySchemes": {
Expand Down
2 changes: 1 addition & 1 deletion agdb_server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(
paths(
crate::routes::status,
crate::routes::admin::shutdown,
crate::routes::user::create,
crate::routes::user::login,
Expand All @@ -19,7 +20,6 @@ use utoipa::OpenApi;
components(schemas(
crate::routes::user::ChangePassword,
crate::routes::user::UserCredentials,
crate::routes::user::UserToken,
crate::routes::db::DbType,
crate::routes::db::ServerDatabase,
crate::routes::db::ServerDatabaseName,
Expand Down
5 changes: 4 additions & 1 deletion agdb_server/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::api::Api;
use crate::config::Config;
use crate::db::DbPool;
use crate::logger;
use crate::routes;
Expand All @@ -10,9 +11,10 @@ use tokio::sync::broadcast::Sender;
use utoipa::OpenApi;
use utoipa_rapidoc::RapiDoc;

pub(crate) fn app(shutdown_sender: Sender<()>, db_pool: DbPool) -> Router {
pub(crate) fn app(config: Config, shutdown_sender: Sender<()>, db_pool: DbPool) -> Router {
let state = ServerState {
db_pool,
config,
shutdown_sender,
};

Expand Down Expand Up @@ -40,6 +42,7 @@ pub(crate) fn app(shutdown_sender: Sender<()>, db_pool: DbPool) -> Router {
"/v1",
Router::new()
.route("/test_error", routing::get(routes::test_error))
.route("/status", routing::get(routes::status))
.nest("/admin", admin_router_v1)
.nest("/user", user_router_v1)
.nest("/db", db_router_v1),
Expand Down
2 changes: 2 additions & 0 deletions agdb_server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const CONFIG_FILE: &str = "agdb_server.yaml";
pub(crate) struct ConfigImpl {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) admin: String,
}

pub(crate) fn new() -> ServerResult<Config> {
Expand All @@ -21,6 +22,7 @@ pub(crate) fn new() -> ServerResult<Config> {
let config = ConfigImpl {
host: "127.0.0.1".to_string(),
port: 3000,
admin: "admin".to_string(),
};
std::fs::write(CONFIG_FILE, serde_yaml::to_string(&config)?)?;

Expand Down
41 changes: 34 additions & 7 deletions agdb_server/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod server_db;
mod server_db_storage;

use crate::config::Config;
use crate::db::server_db::ServerDb;
use crate::password::Password;
use crate::server_error::ServerResult;
use agdb::DbId;
use agdb::QueryBuilder;
Expand Down Expand Up @@ -41,7 +43,7 @@ pub(crate) struct DbPoolImpl {
pub(crate) struct DbPool(pub(crate) Arc<DbPoolImpl>);

impl DbPool {
pub(crate) fn new() -> ServerResult<Self> {
pub(crate) fn new(config: &Config) -> ServerResult<Self> {
let db_exists = Path::new("agdb_server.agdb").exists();

let db_pool = Self(Arc::new(DbPoolImpl {
Expand All @@ -50,12 +52,37 @@ impl DbPool {
}));

if !db_exists {
db_pool.0.server_db.get_mut()?.exec_mut(
&QueryBuilder::insert()
.nodes()
.aliases(vec!["users", "dbs"])
.query(),
)?;
let admin_password = Password::create(&config.admin, &config.admin);

db_pool.0.server_db.get_mut()?.transaction_mut(|t| {
t.exec_mut(
&QueryBuilder::insert()
.nodes()
.aliases(vec!["users", "dbs"])
.query(),
)?;

let admin = t.exec_mut(
&QueryBuilder::insert()
.nodes()
.values(&DbUser {
db_id: None,
name: config.admin.clone(),
password: admin_password.password.to_vec(),
salt: admin_password.user_salt.to_vec(),
token: String::new(),
})
.query(),
)?;

t.exec_mut(
&QueryBuilder::insert()
.edges()
.from("users")
.to(admin)
.query(),
)
})?;
}

Ok(db_pool)
Expand Down
6 changes: 3 additions & 3 deletions agdb_server/src/db/server_db_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ mod tests {
let _test_file_dot = TestFile::new(".file_storage.agdb");
let test_file2 = TestFile::new("file_storage_backup.agdb");
let mut storage = ServerDbStorage::new(&format!("file:{}", test_file.0))?;
println!("{:?}", storage);
format!("{:?}", storage);
storage.backup(&test_file2.0)?;
assert!(std::path::Path::new(&test_file2.0).exists());
storage.flush()?;
Expand All @@ -145,7 +145,7 @@ mod tests {
let _test_file_dot = TestFile::new(".mapped_storage.agdb");
let test_file2 = TestFile::new("mapped_storage_backup.agdb");
let mut storage = ServerDbStorage::new(&format!("mapped:{}", test_file.0))?;
println!("{:?}", storage);
format!("{:?}", storage);
storage.backup(&test_file2.0)?;
assert!(std::path::Path::new(&test_file2.0).exists());
storage.flush()?;
Expand All @@ -161,7 +161,7 @@ mod tests {
#[test]
fn memory_storage() -> anyhow::Result<()> {
let mut storage = ServerDbStorage::new("memory:db_test.agdb")?;
println!("{:?}", storage);
format!("{:?}", storage);
storage.backup("backup_test")?;
storage.flush()?;
assert!(storage.is_empty());
Expand Down
4 changes: 2 additions & 2 deletions agdb_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ async fn main() -> ServerResult {

let (shutdown_sender, mut shutdown_receiver) = tokio::sync::broadcast::channel::<()>(1);
let config = config::new()?;
let db_pool = DbPool::new()?;
let app = app::app(shutdown_sender, db_pool);
let db_pool = DbPool::new(&config)?;
let address = format!("{}:{}", config.host, config.port);
let app = app::app(config, shutdown_sender, db_pool);
tracing::info!("Listening at {address}");
let listener = tokio::net::TcpListener::bind(address).await?;

Expand Down
10 changes: 10 additions & 0 deletions agdb_server/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ pub(crate) mod admin;
pub(crate) mod db;
pub(crate) mod user;

#[utoipa::path(get,
path = "/api/v1/status",
responses(
(status = 200, description = "Server is ready"),
)
)]
pub(crate) async fn status() -> StatusCode {
StatusCode::OK
}

pub(crate) async fn test_error() -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
12 changes: 9 additions & 3 deletions agdb_server/src/routes/admin.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use crate::user_id::AdminId;
use axum::extract::State;
use axum::http::StatusCode;
use tokio::sync::broadcast::Sender;

#[utoipa::path(get,
path = "/api/v1/admin/shutdown",
security(("Token" = [])),
responses(
(status = 200, description = "Server is shutting down"),
)
)]
pub(crate) async fn shutdown(State(shutdown_sender): State<Sender<()>>) -> StatusCode {
pub(crate) async fn shutdown(
_admin_id: AdminId,
State(shutdown_sender): State<Sender<()>>,
) -> StatusCode {
match shutdown_sender.send(()) {
Ok(_) => StatusCode::OK,
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
Expand All @@ -18,12 +23,13 @@ pub(crate) async fn shutdown(State(shutdown_sender): State<Sender<()>>) -> Statu
#[cfg(test)]
mod tests {
use super::*;
use agdb::DbId;

#[tokio::test]
async fn shutdown_test() -> anyhow::Result<()> {
let (shutdown_sender, _shutdown_receiver) = tokio::sync::broadcast::channel::<()>(1);

let status = shutdown(State(shutdown_sender)).await;
let status = shutdown(AdminId(DbId(0)), State(shutdown_sender)).await;

assert_eq!(status, StatusCode::OK);
Ok(())
Expand All @@ -33,7 +39,7 @@ mod tests {
async fn bad_shutdown() -> anyhow::Result<()> {
let shutdown_sender = Sender::<()>::new(1);

let status = shutdown(State(shutdown_sender)).await;
let status = shutdown(AdminId(DbId(0)), State(shutdown_sender)).await;

assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
Ok(())
Expand Down
14 changes: 5 additions & 9 deletions agdb_server/src/routes/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use axum::extract::State;
use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde::Serialize;
use utoipa::ToSchema;
use uuid::Uuid;

Expand All @@ -16,9 +15,6 @@ pub(crate) struct UserCredentials {
pub(crate) password: String,
}

#[derive(Default, Serialize, ToSchema)]
pub(crate) struct UserToken(String);

#[derive(Deserialize, ToSchema)]
pub(crate) struct ChangePassword {
pub(crate) name: String,
Expand All @@ -30,33 +26,33 @@ pub(crate) struct ChangePassword {
path = "/api/v1/user/login",
request_body = UserCredentials,
responses(
(status = 200, description = "Login successful", body = UserToken),
(status = 200, description = "Login successful", body = String),
(status = 401, description = "Bad password"),
(status = 403, description = "User not found")
)
)]
pub(crate) async fn login(
State(db_pool): State<DbPool>,
Json(request): Json<UserCredentials>,
) -> Result<(StatusCode, Json<UserToken>), ServerError> {
) -> Result<(StatusCode, String), ServerError> {
let user = db_pool.find_user(&request.name);

if user.is_err() {
return Ok((StatusCode::FORBIDDEN, Json::default()));
return Ok((StatusCode::FORBIDDEN, String::new()));
}

let user = user?;
let pswd = Password::new(&user.name, &user.password, &user.salt)?;

if !pswd.verify_password(&request.password) {
return Ok((StatusCode::UNAUTHORIZED, Json::default()));
return Ok((StatusCode::UNAUTHORIZED, String::new()));
}

let token_uuid = Uuid::new_v4();
let token = token_uuid.to_string();
db_pool.save_token(user.db_id.unwrap(), &token)?;

Ok((StatusCode::OK, Json(UserToken(token))))
Ok((StatusCode::OK, token))
}

#[utoipa::path(post,
Expand Down
8 changes: 8 additions & 0 deletions agdb_server/src/server_state.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::config::Config;
use crate::db::DbPool;
use axum::extract::FromRef;
use tokio::sync::broadcast::Sender;

#[derive(Clone)]
pub(crate) struct ServerState {
pub(crate) db_pool: DbPool,
pub(crate) config: Config,
pub(crate) shutdown_sender: Sender<()>,
}

Expand All @@ -19,3 +21,9 @@ impl FromRef<ServerState> for Sender<()> {
input.shutdown_sender.clone()
}
}

impl FromRef<ServerState> for Config {
fn from_ref(input: &ServerState) -> Self {
input.config.clone()
}
}
Loading

0 comments on commit b8364a1

Please sign in to comment.