feat(api): implemented a basic api skeleton, that matches the api docs

This commit is contained in:
antifallobst 2023-08-16 14:02:23 +02:00
parent e84ec87ea0
commit 9db5c04cb6
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
9 changed files with 2023 additions and 1 deletions

2
API.md
View File

@ -101,5 +101,5 @@ Deletes the account.
#### Responses #### Responses
##### 200 - Success ##### 200 - Success
The account was deleted. The account was deleted.
##### 403 - Error: Forbidden ##### 401 - Error: Unauthorized
The provided token doesn't allow you to perform this operation. The provided token doesn't allow you to perform this operation.

1791
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "nerdcult_api"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "sync"] }
anyhow = "1.0"
actix-web = "4"
serde = { version = "1.0.183", features = ["derive"] }
libinjection = "0.3.2"
sha2 = "0.10.2"
pbkdf2 = { version = "0.12", features = ["simple"] }
env_logger = "0.10"
log = "0.4"
clap = { version = "4.3.21", features = ["derive"] }
actix-web-httpauth = "0.8.0"

70
src/api/account/calls.rs Normal file
View File

@ -0,0 +1,70 @@
use crate::api::account::{data, handlers};
use crate::api::ApiState;
use actix_web::{delete, post, web, HttpResponse, Responder};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use log::error;
#[post("/account/register")]
async fn register(
data: web::Data<ApiState>,
body: web::Json<data::RegisterRequest>,
) -> impl Responder {
match handlers::register(body.into_inner()).await {
Ok(resp) => match resp {
data::RegisterResponse::Success => HttpResponse::Ok().finish(),
data::RegisterResponse::PasswordTooWeak => HttpResponse::Forbidden().finish(),
data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)),
data::RegisterResponse::MalformedEmail => HttpResponse::UnprocessableEntity().finish(),
},
Err(e) => {
error!("While handling register request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}
#[post("/account/verify")]
async fn verify(data: web::Data<ApiState>, body: web::Json<data::VerifyRequest>) -> impl Responder {
match handlers::verify(body.into_inner()).await {
Ok(resp) => match resp {
data::VerifyResponse::Success => HttpResponse::Ok().finish(),
data::VerifyResponse::TokenUnknown => HttpResponse::Forbidden().finish(),
},
Err(e) => {
error!("While handling verify request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}
#[post("/account/authenticate")]
async fn authenticate(
data: web::Data<ApiState>,
body: web::Json<data::AuthenticateRequest>,
) -> impl Responder {
match handlers::authenticate(body.into_inner()).await {
Ok(resp) => match resp {
data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
data::AuthenticateResponse::WrongPassword => HttpResponse::Forbidden().finish(),
data::AuthenticateResponse::UserNotFound => HttpResponse::NotFound().finish(),
},
Err(e) => {
error!("While handling authenticate request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}
#[delete("/account/delete")]
async fn delete(data: web::Data<ApiState>, auth: BearerAuth) -> impl Responder {
match handlers::delete(auth.token().to_string()).await {
Ok(resp) => match resp {
data::DeleteResponse::Success => HttpResponse::Ok().finish(),
data::DeleteResponse::Unauthorized => HttpResponse::Unauthorized().finish(),
},
Err(e) => {
error!("While handling delete request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}

58
src/api/account/data.rs Normal file
View File

@ -0,0 +1,58 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
username: String,
password: String,
email: String,
}
#[derive(Debug, Serialize)]
#[serde(tag = "conflict", rename_all = "snake_case")]
pub enum RegisterConflict {
Username,
Email,
}
#[derive(Debug)]
pub enum RegisterResponse {
Success,
PasswordTooWeak,
Conflict(RegisterConflict),
MalformedEmail,
}
#[derive(Debug, Deserialize)]
pub struct VerifyRequest {
token: String,
}
#[derive(Debug)]
pub enum VerifyResponse {
Success,
TokenUnknown,
}
#[derive(Debug, Deserialize)]
pub struct AuthenticateRequest {
username: String,
password: String,
}
#[derive(Debug, Serialize)]
pub struct AuthenticateSuccess {
pub token: String,
}
#[derive(Debug)]
pub enum AuthenticateResponse {
Success(AuthenticateSuccess),
WrongPassword,
UserNotFound,
}
#[derive(Debug)]
pub enum DeleteResponse {
Success,
Unauthorized,
}

View File

@ -0,0 +1,26 @@
use crate::api::account::data;
use anyhow::Result;
use log::info;
pub async fn register(request: data::RegisterRequest) -> Result<data::RegisterResponse> {
Ok(data::RegisterResponse::Success)
}
pub async fn verify(request: data::VerifyRequest) -> Result<data::VerifyResponse> {
Ok(data::VerifyResponse::Success)
}
pub async fn authenticate(
request: data::AuthenticateRequest,
) -> Result<data::AuthenticateResponse> {
Ok(data::AuthenticateResponse::Success(
data::AuthenticateSuccess {
token: "not_a_valid_token".to_string(),
},
))
}
pub async fn delete(token: String) -> Result<data::DeleteResponse> {
info!("Token: {}", token);
Ok(data::DeleteResponse::Success)
}

3
src/api/account/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod calls;
pub mod data;
mod handlers;

25
src/api/mod.rs Normal file
View File

@ -0,0 +1,25 @@
mod account;
use actix_web::{web, App, HttpServer};
use anyhow::Result;
use log::info;
struct ApiState {}
pub async fn start(port: u16) -> Result<()> {
info!("HTTP server starting on port {} ...", port);
let _ = HttpServer::new(move || {
App::new()
.service(account::calls::register)
.service(account::calls::verify)
.service(account::calls::authenticate)
.service(account::calls::delete)
.app_data(web::Data::new(ApiState {}))
})
.bind(("127.0.0.1", port))?
.run()
.await;
Ok(())
}

30
src/main.rs Normal file
View File

@ -0,0 +1,30 @@
mod api;
use anyhow::Result;
use clap::Parser;
use log::info;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Sets the port on which the API listens
#[arg(short, long)]
port: Option<u16>,
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let args = Args::parse();
info!("Starting NerdcultAPI v0.1");
let mut port: u16 = 8080;
if let Some(p) = args.port {
port = p;
}
api::start(port).await?;
Ok(())
}