simple-auth/src/http/response.rs

148 lines
4.2 KiB
Rust

//! response handles the incoming request parsed `HTTPRequest`
//! it will check if the `HTTPRequest` is valid and build an HTTPResponse corresponding to the HTTP
//! message specs. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
//! NOTE: only few parts of the specification has been implemented
use crate::http::request::{HTTPRequest, HTTPVersion};
use async_trait::async_trait;
use json;
// add the Store trait to be used by `FileStore`
use crate::stores::FileStore;
use crate::stores::Store;
#[derive(Debug, PartialEq)]
pub enum HTTPStatusCode {
Http200,
Http400,
Http403,
Http404,
Http500,
}
impl Into<String> for HTTPStatusCode {
fn into(self) -> String {
match self {
Self::Http200 => "200".to_string(),
Self::Http400 => "400".to_string(),
Self::Http404 => "404".to_string(),
Self::Http403 => "403".to_string(),
Self::Http500 => "500".to_string(),
}
}
}
pub struct HTTPStatusLine {
version: HTTPVersion,
status_code: HTTPStatusCode,
}
impl Default for HTTPStatusLine {
fn default() -> HTTPStatusLine {
HTTPStatusLine {
version: HTTPVersion::Http1_1,
status_code: HTTPStatusCode::Http400,
}
}
}
impl Into<String> for HTTPStatusLine {
fn into(self) -> String {
let version: String = self.version.into();
let status_code: String = self.status_code.into();
format! {"{} {}", version, status_code}
}
}
impl HTTPStatusLine {
fn set_status_code(&mut self, code: HTTPStatusCode) {
self.status_code = code;
}
pub fn get_status_code(&self) -> &HTTPStatusCode {
&self.status_code
}
}
/// represents an HTTP response (headers are not parsed)
/// NOTE: for simplicity, only JSON body are accepted
pub struct HTTPResponse {
pub status_line: HTTPStatusLine,
body: json::JsonValue,
}
impl Default for HTTPResponse {
fn default() -> Self {
HTTPResponse {
status_line: HTTPStatusLine::default(),
body: json::parse(r#"{"error": "the incoming request is not valid"}"#).unwrap(),
}
}
}
impl Into<String> for HTTPResponse {
fn into(self) -> String {
// move `self.body` into a new var
let b = self.body;
let body: String = json::stringify(b);
let status_line: String = self.status_line.into();
format!(
"{}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
status_line,
body.len(),
body
)
}
}
impl HTTPResponse {
/// creates a response from the incoming `Request`
/// `From<T>` could be used instead of forcing it like this
/// it fails using `async_trait` attributes (only custom traits work ?)
pub async fn from(request: HTTPRequest) -> Self {
let mut response = HTTPResponse::default();
if !request.is_valid() {
return response;
}
// empty body -> invalid request (credentials needed)
if let None = request.body {
return Self::as_403();
}
// TODO: path to `store.txt` must not be hardcoded, should be in a config file and load at
// runtime
let mut store = FileStore::new("tests/data/store.txt".to_string());
let body = request.body.unwrap();
let is_auth = store.is_auth(&body.get_data()).await;
if !is_auth {
return Self::as_403();
}
// TODO: must be a valid JWT (to implement)
let body = json::parse(
r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#,
)
.unwrap();
response.status_line.version = request.start_line.version;
response.status_line.status_code = HTTPStatusCode::Http200;
response.body = body;
response
}
/// generates a 403 response with a correct error message
pub fn as_403() -> Self {
let mut response = HTTPResponse {
status_line: HTTPStatusLine::default(),
body: json::parse(r#"{"error": "invalid credentials"}"#).unwrap(),
};
response
.status_line
.set_status_code(HTTPStatusCode::Http403);
response
}
}