diff --git a/API.md b/API.md index 6751cc9..ae2d2ec 100644 --- a/API.md +++ b/API.md @@ -152,9 +152,9 @@ Lists all active auth tokens for the account. #### Responses ##### 200 - Success __Content - JSON:__ -| Field | Description | -|--------|-------------------------------------------| -| tokens | A list of (token, expiration date) pairs. | +| Field | Description | +|--------|-------------------------------------------------------------------------------------------------| +| tokens | A list of (token, expiration date) pairs. The expiration date is given as a UTC UNIX timestamp. | ##### 401 - Error: Unauthorized The provided auth token doesn't allow you to perform this operation. ##### 403 - Error: Forbidden diff --git a/src/api/account/calls.rs b/src/api/account/calls.rs index ceeef56..125f5a5 100644 --- a/src/api/account/calls.rs +++ b/src/api/account/calls.rs @@ -1,6 +1,6 @@ use crate::api::account::{data, handlers}; use crate::api::ApiState; -use actix_web::{delete, post, web, HttpResponse, Responder}; +use actix_web::{delete, get, post, web, HttpResponse, Responder}; use actix_web_httpauth::extractors::bearer::BearerAuth; use log::error; @@ -48,7 +48,9 @@ async fn authenticate( data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), data::AuthenticateResponse::WrongPassword => HttpResponse::Unauthorized().finish(), data::AuthenticateResponse::UserNotFound => HttpResponse::NotFound().finish(), - data::AuthenticateResponse::NotVerified => HttpResponse::new(actix_web::http::StatusCode::FAILED_DEPENDENCY), + data::AuthenticateResponse::NotVerified => { + HttpResponse::new(actix_web::http::StatusCode::FAILED_DEPENDENCY) + } data::AuthenticateResponse::Blocked => HttpResponse::Forbidden().finish(), }, Err(e) => { @@ -72,3 +74,38 @@ async fn delete(data: web::Data, auth: BearerAuth) -> impl Responder { } } } + +#[delete("/account/tokens")] +async fn tokens_delete( + data: web::Data, + auth: BearerAuth, + body: web::Json, +) -> impl Responder { + match handlers::token_delete(&data.pool, auth.token().to_string(), body.into_inner()).await { + Ok(resp) => match resp { + data::TokenDeleteResponse::Success => HttpResponse::Ok().finish(), + data::TokenDeleteResponse::Unauthorized => HttpResponse::Unauthorized().finish(), + data::TokenDeleteResponse::Blocked => HttpResponse::Forbidden().finish(), + data::TokenDeleteResponse::TokenNotFound => HttpResponse::NotFound().finish(), + }, + Err(e) => { + error!("While handling token delete request: {e}"); + HttpResponse::InternalServerError().finish() + } + } +} + +#[get("/account/tokens")] +async fn tokens_get(data: web::Data, auth: BearerAuth) -> impl Responder { + match handlers::token_list(&data.pool, auth.token().to_string()).await { + Ok(resp) => match resp { + data::TokenListResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), + data::TokenListResponse::Unauthorized => HttpResponse::Unauthorized().finish(), + data::TokenListResponse::Blocked => HttpResponse::Forbidden().finish(), + }, + Err(e) => { + error!("While handling token list request: {e}"); + HttpResponse::InternalServerError().finish() + } + } +} diff --git a/src/api/account/data.rs b/src/api/account/data.rs index ed5826c..ce8818b 100644 --- a/src/api/account/data.rs +++ b/src/api/account/data.rs @@ -60,3 +60,28 @@ pub enum DeleteResponse { Blocked, Unauthorized, } + +#[derive(Debug, Deserialize)] +pub struct TokenDeleteRequest { + pub token: String, +} + +#[derive(Debug)] +pub enum TokenDeleteResponse { + Success, + Unauthorized, + Blocked, + TokenNotFound, +} + +#[derive(Debug, Serialize)] +pub struct TokenListSuccess { + pub tokens: Vec<(String, i64)>, +} + +#[derive(Debug)] +pub enum TokenListResponse { + Success(TokenListSuccess), + Unauthorized, + Blocked, +} diff --git a/src/api/account/handlers.rs b/src/api/account/handlers.rs index db2a7be..74ab92c 100644 --- a/src/api/account/handlers.rs +++ b/src/api/account/handlers.rs @@ -130,12 +130,12 @@ pub async fn authenticate( )) } -pub async fn delete(pool: &MySqlPool, token: String) -> Result { - if !token.is_alpha() { +pub async fn delete(pool: &MySqlPool, auth: String) -> Result { + if !auth.is_alpha() { return Ok(data::DeleteResponse::Blocked); } - let token = match AuthToken::check(pool, &token).await? { + let token = match AuthToken::check(pool, &auth).await? { Some(t) => t, None => return Ok(data::DeleteResponse::Unauthorized), }; @@ -153,3 +153,57 @@ pub async fn delete(pool: &MySqlPool, token: String) -> Result Result { + if !auth.is_alpha() { + return Ok(data::TokenDeleteResponse::Blocked); + } + + let token = match AuthToken::check(pool, &auth).await? { + Some(t) => t, + None => return Ok(data::TokenDeleteResponse::Unauthorized), + }; + + let delete_token = match AuthToken::check(pool, &request.token).await? { + Some(t) => { + if t.account == token.account { + t + } else { + return Ok(data::TokenDeleteResponse::TokenNotFound); + } + } + None => return Ok(data::TokenDeleteResponse::TokenNotFound), + }; + + delete_token.delete(pool).await?; + + Ok(data::TokenDeleteResponse::Success) +} + +pub async fn token_list(pool: &MySqlPool, auth: String) -> Result { + if !auth.is_alpha() { + return Ok(data::TokenListResponse::Blocked); + } + + let token = match AuthToken::check(pool, &auth).await? { + Some(t) => t, + None => return Ok(data::TokenListResponse::Unauthorized), + }; + + let list: Vec<(String, i64)> = sqlx::query_as!( + AuthToken, + r#"SELECT * FROM AuthTokens WHERE account = ?;"#, + token.account + ) + .fetch_all(pool) + .await? + .iter() + .map(|t| (t.token.clone(), t.expire.timestamp())) + .collect(); + + Ok(data::TokenListResponse::Success(data::TokenListSuccess {tokens: list})) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 6467dc3..fbc3608 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -59,6 +59,8 @@ pub async fn start(port: u16, pool: MySqlPool) -> Result<()> { .service(account::calls::verify) .service(account::calls::authenticate) .service(account::calls::delete) + .service(account::calls::tokens_delete) + .service(account::calls::tokens_get) .app_data(web::Data::new(ApiState { pool: pool.clone() })) }) .bind(("127.0.0.1", port))?