diff --git a/docs/README.md b/docs/README.md index 7010622..2c33d6e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ __(ND)__ -> Not designed yet. - [ ] `/deactivate` - [ ] `/activate` - `/user/` - - [ ] `/info` + - [X] `/info` - [ ] `/follow` - [ ] `/follows` - [ ] `/followers` diff --git a/docs/user/info.md b/docs/user/info.md index bd4cd64..c68322c 100644 --- a/docs/user/info.md +++ b/docs/user/info.md @@ -1,28 +1,37 @@ # `/user/info` - GET -Returns information about the project. +Returns information about the user. ## Urlencoded Parameters + You have to set one parameter. Setting none or two parameters will result in a _400 Bad Request_ Response. + | Parameter | Description | |-----------|--------------------------------------------------------------------| | name | The username of the user on which the operation will be performed. | | id | The userid of the user on which the operation will be performed. | - ## Responses + ### 200 - Success + __Content - JSON:__ -| Field | Description | +| Field | Description | |----------|-------------------------------------------------------------------| -| id | The users unique id. | -| name | The users unique username. | -| joined | The datetime when the user joined. Represented as UNIX timestamp. | -| is_admin | A boolean if the user is an admin. | +| id | The users unique id. | +| name | The users unique username. | +| joined | The datetime when the user joined. Represented as UNIX timestamp. | +| is_admin | A boolean if the user is an admin. | + ### 400 - Error: Bad Request + The request was malformed. + ### 403 - Error: Forbidden + Blocked for security reasons. + ### 404 - Error: Not Found + The user wasn't found. \ No newline at end of file diff --git a/src/accounts.rs b/src/accounts.rs index 399c6d9..6d89906 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -13,6 +13,10 @@ pub struct ID { flags: u16, } +pub enum AccountFlags { + Admin, +} + #[derive(Debug)] pub struct Account { pub id: ID, @@ -35,6 +39,14 @@ impl From for ID { } } +impl From for u16 { + fn from(value: AccountFlags) -> Self { + match value { + AccountFlags::Admin => 0b0000_0000_0000_0001, + } + } +} + impl ID { pub fn new(id: i64, flags: u16) -> Self { Self { @@ -200,4 +212,9 @@ impl Account { .await?; Ok(()) } + + pub fn has_flag(&self, flag: AccountFlags) -> bool { + let mask: u16 = flag.into(); + (self.flags as u16 & mask) > 0 + } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 59fdeea..1ed1433 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,6 @@ mod account; mod project; +mod user; use actix_web::{web, App, HttpServer}; use anyhow::Result; @@ -96,6 +97,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> { .service(project::calls::create) .service(project::calls::info) .service(project::calls::delete) + .service(user::calls::info) .app_data(web::Data::new(ApiState { pool: pool.clone() })) }) .bind(("0.0.0.0", port))? diff --git a/src/api/user/calls.rs b/src/api/user/calls.rs new file mode 100644 index 0000000..dea53a7 --- /dev/null +++ b/src/api/user/calls.rs @@ -0,0 +1,33 @@ +use crate::api::user::{data, handlers}; +use crate::api::ApiState; +use actix_web::{get, web, HttpResponse, Responder}; +use log::error; + +#[get("/user/info")] +async fn info(data: web::Data, query: web::Query) -> impl Responder { + let request = if query.id.is_none() || query.username.is_none() { + if let Some(id) = query.id { + data::InfoRequest::Id(id) + } else if let Some(name) = query.into_inner().username { + data::InfoRequest::Name(name) + } else { + // No parameters were supplied + return HttpResponse::BadRequest().finish(); + } + } else { + // Too many parameters were supplied + return HttpResponse::BadRequest().finish(); + }; + + match handlers::info(&data.pool, request).await { + Ok(resp) => match resp { + data::InfoResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), + data::InfoResponse::Blocked => HttpResponse::Forbidden().finish(), + data::InfoResponse::NotFound => HttpResponse::NotFound().finish(), + }, + Err(e) => { + error!("While handling info request: {e}"); + HttpResponse::InternalServerError().finish() + } + } +} diff --git a/src/api/user/data.rs b/src/api/user/data.rs new file mode 100644 index 0000000..176e31a --- /dev/null +++ b/src/api/user/data.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +pub struct UserIdQuery { + pub username: Option, + pub id: Option, +} + +#[derive(Debug)] +pub enum InfoRequest { + Name(String), + Id(i64), +} + +#[derive(Debug, Serialize)] +pub struct InfoSuccess { + pub id: i64, + pub name: String, + pub joined: i64, + pub is_admin: bool, +} + +#[derive(Debug)] +pub enum InfoResponse { + Success(InfoSuccess), + Blocked, + NotFound, +} diff --git a/src/api/user/handlers.rs b/src/api/user/handlers.rs new file mode 100644 index 0000000..1506eb1 --- /dev/null +++ b/src/api/user/handlers.rs @@ -0,0 +1,31 @@ +use crate::{ + api::user::data, + accounts::{Account, AccountFlags}, + security::is_sql_injection, +}; +use anyhow::Result; +use sqlx::PgPool; + +pub async fn info(pool: &PgPool, request: data::InfoRequest) -> Result { + let account = match match request { + data::InfoRequest::Name(name) => { + if is_sql_injection(&name) { + return Ok(data::InfoResponse::Blocked); + } + Account::from_username(pool, &name).await? + } + data::InfoRequest::Id(id) => Account::from_id(pool, id).await?, + } { + Some(p) => p, + None => return Ok(data::InfoResponse::NotFound), + }; + + let is_admin = account.has_flag(AccountFlags::Admin); + + Ok(data::InfoResponse::Success(data::InfoSuccess { + id: account.id.id(), + name: account.username, + joined: account.joined.timestamp(), + is_admin, + })) +} diff --git a/src/api/user/mod.rs b/src/api/user/mod.rs new file mode 100644 index 0000000..6ffa288 --- /dev/null +++ b/src/api/user/mod.rs @@ -0,0 +1,3 @@ +pub mod calls; +pub mod data; +mod handlers;