feat(api): fully implemented the two (GET and DELETE) `tokens` endpoints

This commit is contained in:
antifallobst 2023-08-17 16:40:05 +02:00
parent d33df76b96
commit ce96035711
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
5 changed files with 126 additions and 8 deletions

4
API.md
View File

@ -153,8 +153,8 @@ Lists all active auth tokens for the account.
##### 200 - Success ##### 200 - Success
__Content - JSON:__ __Content - JSON:__
| Field | Description | | Field | Description |
|--------|-------------------------------------------| |--------|-------------------------------------------------------------------------------------------------|
| tokens | A list of (token, expiration date) pairs. | | tokens | A list of (token, expiration date) pairs. The expiration date is given as a UTC UNIX timestamp. |
##### 401 - Error: Unauthorized ##### 401 - Error: Unauthorized
The provided auth token doesn't allow you to perform this operation. The provided auth token doesn't allow you to perform this operation.
##### 403 - Error: Forbidden ##### 403 - Error: Forbidden

View File

@ -1,6 +1,6 @@
use crate::api::account::{data, handlers}; use crate::api::account::{data, handlers};
use crate::api::ApiState; 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 actix_web_httpauth::extractors::bearer::BearerAuth;
use log::error; use log::error;
@ -48,7 +48,9 @@ async fn authenticate(
data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
data::AuthenticateResponse::WrongPassword => HttpResponse::Unauthorized().finish(), data::AuthenticateResponse::WrongPassword => HttpResponse::Unauthorized().finish(),
data::AuthenticateResponse::UserNotFound => HttpResponse::NotFound().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(), data::AuthenticateResponse::Blocked => HttpResponse::Forbidden().finish(),
}, },
Err(e) => { Err(e) => {
@ -72,3 +74,38 @@ async fn delete(data: web::Data<ApiState>, auth: BearerAuth) -> impl Responder {
} }
} }
} }
#[delete("/account/tokens")]
async fn tokens_delete(
data: web::Data<ApiState>,
auth: BearerAuth,
body: web::Json<data::TokenDeleteRequest>,
) -> 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<ApiState>, 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()
}
}
}

View File

@ -60,3 +60,28 @@ pub enum DeleteResponse {
Blocked, Blocked,
Unauthorized, 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,
}

View File

@ -130,12 +130,12 @@ pub async fn authenticate(
)) ))
} }
pub async fn delete(pool: &MySqlPool, token: String) -> Result<data::DeleteResponse> { pub async fn delete(pool: &MySqlPool, auth: String) -> Result<data::DeleteResponse> {
if !token.is_alpha() { if !auth.is_alpha() {
return Ok(data::DeleteResponse::Blocked); return Ok(data::DeleteResponse::Blocked);
} }
let token = match AuthToken::check(pool, &token).await? { let token = match AuthToken::check(pool, &auth).await? {
Some(t) => t, Some(t) => t,
None => return Ok(data::DeleteResponse::Unauthorized), None => return Ok(data::DeleteResponse::Unauthorized),
}; };
@ -153,3 +153,57 @@ pub async fn delete(pool: &MySqlPool, token: String) -> Result<data::DeleteRespo
Ok(data::DeleteResponse::Success) Ok(data::DeleteResponse::Success)
} }
pub async fn token_delete(
pool: &MySqlPool,
auth: String,
request: data::TokenDeleteRequest,
) -> Result<data::TokenDeleteResponse> {
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<data::TokenListResponse> {
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}))
}

View File

@ -59,6 +59,8 @@ pub async fn start(port: u16, pool: MySqlPool) -> Result<()> {
.service(account::calls::verify) .service(account::calls::verify)
.service(account::calls::authenticate) .service(account::calls::authenticate)
.service(account::calls::delete) .service(account::calls::delete)
.service(account::calls::tokens_delete)
.service(account::calls::tokens_get)
.app_data(web::Data::new(ApiState { pool: pool.clone() })) .app_data(web::Data::new(ApiState { pool: pool.clone() }))
}) })
.bind(("127.0.0.1", port))? .bind(("127.0.0.1", port))?