feat(api): implemented a basic api skeleton, that matches the api docs
This commit is contained in:
parent
e84ec87ea0
commit
9db5c04cb6
2
API.md
2
API.md
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod calls;
|
||||||
|
pub mod data;
|
||||||
|
mod handlers;
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
Loading…
Reference in New Issue