Skip to content

Commit

Permalink
Add admin roles and new RPC (add_admin)
Browse files Browse the repository at this point in the history
  • Loading branch information
bubelov committed Sep 29, 2024
1 parent d8f6d8d commit 130c1f4
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 28 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,12 @@ deadpool-sqlite = { version = "0.8.1", default-features = false, features = ["rt
actix-governor = { version = "0.5.0", default-features = false }

# https://crates.io/crates/jsonrpc-v2
jsonrpc-v2 = { version = "0.13.0", default-features = false, features = ["actix-web-v4-integration", "easy-errors"] }
jsonrpc-v2 = { version = "0.13.0", default-features = false, features = ["actix-web-v4-integration", "easy-errors"] }

[dependencies.uuid]
version = "1.10.0"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]
8 changes: 8 additions & 0 deletions migrations/61.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE token ADD COLUMN allowed_methods TEXT NOT NULL DEFAULT '[]';

DROP TRIGGER token_updated_at;

CREATE TRIGGER token_updated_at UPDATE OF user_id, secret, allowed_methods, created_at, deleted_at ON token
BEGIN
UPDATE token SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = old.id;
END;
75 changes: 49 additions & 26 deletions src/auth/model.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,97 @@
use crate::Result;
use rusqlite::{named_params, Connection, OptionalExtension, Row};
use tracing::debug;
use serde_json::Value;
#[cfg(not(test))]
use std::thread::sleep;
#[cfg(not(test))]
use std::time::Duration;

#[allow(dead_code)]
pub struct Token {
pub id: i64,
pub owner: String,
pub secret: String,
pub allowed_methods: Vec<String>,
pub created_at: String,
pub updated_at: String,
pub deleted_at: Option<String>,
}

impl Token {
#[cfg(test)]
pub fn insert(owner: &str, secret: &str, conn: &Connection) -> Result<Token> {
use crate::Error;
const TABLE: &str = "token";
const ALL_COLUMNS: &str = "id, owner, secret, allowed_methods, created_at, updated_at, deleted_at";
const _COL_ID: &str = "id";
const COL_OWNER: &str = "owner";
const COL_SECRET: &str = "secret";
const COL_ALLOWED_METHODS: &str = "allowed_methods";
const _COL_CREATED_AT: &str = "created_at";
const _COL_UPDATED_AT: &str = "updated_at";
const _COL_DELETED_AT: &str = "deleted_at";

impl Token {
pub fn insert(
owner: &str,
secret: &str,
allowed_methods: Vec<String>,
conn: &Connection,
) -> Result<Option<Token>> {
let allowed_methods =
Value::Array(allowed_methods.into_iter().map(|it| it.into()).collect());
let query = format!(
r#"
INSERT INTO token (
owner,
secret
{COL_OWNER},
{COL_SECRET},
{COL_ALLOWED_METHODS}
) VALUES (
:owner,
:secret
:secret,
:allowed_methods
)
"#
);
debug!(query);
#[cfg(not(test))]
sleep(Duration::from_millis(10));
conn.execute(
&query,
named_params! {
":owner": owner,
":secret": secret,
":allowed_methods": allowed_methods,
},
)?;
Ok(Token::select_by_secret(secret, conn)?
.ok_or(Error::Rusqlite(rusqlite::Error::QueryReturnedNoRows))?)
Ok(Token::select_by_secret(secret, conn)?)
}

pub fn select_by_secret(secret: &str, conn: &Connection) -> Result<Option<Token>> {
let query = format!(
r#"
SELECT
t.id,
t.owner,
t.secret,
t.created_at,
t.updated_at,
t.deleted_at
FROM token t
WHERE secret = :secret
SELECT {ALL_COLUMNS}
FROM {TABLE}
WHERE {COL_SECRET} = :secret
"#
);
debug!(query);
Ok(conn
let res = conn
.query_row(&query, named_params! { ":secret": secret }, Self::mapper())
.optional()?)
.optional()?;
Ok(res)
}

const fn mapper() -> fn(&Row) -> rusqlite::Result<Token> {
|row: &Row| -> rusqlite::Result<Token> {
let allowed_methods: Value = row.get(3)?;
Ok(Token {
id: row.get(0)?,
owner: row.get(1)?,
secret: row.get(2)?,
created_at: row.get(3)?,
updated_at: row.get(4)?,
deleted_at: row.get(5)?,
allowed_methods: allowed_methods
.as_array()
.unwrap()
.into_iter()
.map(|it| it.as_str().unwrap().into())
.collect(),
created_at: row.get(4)?,
updated_at: row.get(5)?,
deleted_at: row.get(6)?,
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/auth/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ pub async fn mock_token(secret: &str, pool: &Pool) -> Token {
pool.get()
.await
.unwrap()
.interact(move |conn| Token::insert("test", &secret, conn))
.interact(move |conn| Token::insert("test", &secret, vec![], conn))
.await
.unwrap()
.unwrap()
.unwrap()
}

pub async fn check(req: &HttpRequest, pool: &Pool) -> Result<Token, Error> {
Expand Down
58 changes: 58 additions & 0 deletions src/rpc/add_admin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::{auth::Token, discord, Result};
use deadpool_sqlite::Pool;
use jsonrpc_v2::{Data, Params};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::info;
use uuid::Uuid;

#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Args {
pub token: String,
pub new_admin_name: String,
pub new_admin_token: String,
pub new_admin_allowed_methods: Vec<String>,
}

#[derive(Serialize)]
pub struct Res {
pub name: String,
pub token: String,
pub allowed_methods: Vec<String>,
}

pub async fn run(Params(args): Params<Args>, pool: Data<Arc<Pool>>) -> Result<Res> {
let token = pool
.get()
.await?
.interact(move |conn| Token::select_by_secret(&args.token, conn))
.await??
.unwrap();
let new_token = pool
.get()
.await?
.interact(move |conn| {
Token::insert(
&args.new_admin_name,
&Uuid::new_v4().to_string(),
args.new_admin_allowed_methods,
conn,
)
})
.await??
.unwrap();
let log_message = format!(
"{} added new admin user {} with the following allowed methods: {}",
token.owner,
new_token.owner,
serde_json::to_string(&new_token.allowed_methods)?,
);
info!(log_message);
discord::send_message_to_channel(&log_message, discord::CHANNEL_API).await;
Ok(Res {
name: new_token.owner,
token: new_token.secret,
allowed_methods: new_token.allowed_methods,
})
}
1 change: 1 addition & 0 deletions src/rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod add_admin;
pub mod add_area;
pub mod add_element_comment;
pub mod boost_element;
Expand Down
1 change: 1 addition & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub async fn run() -> Result<()> {
rpc::generate_element_categories::run,
)
.with_method("syncelements", rpc::sync_elements::run)
.with_method("addadmin", rpc::add_admin::run)
.finish()
.into_actix_web_service(),
),
Expand Down

0 comments on commit 130c1f4

Please sign in to comment.