EinsatzOnline/src/modules/api/users/read.rs

269 lines
8.4 KiB
Rust

use rocket::serde::json::Json;
use rocket::{State, request, Request, Response, response};
use crate::helper::settings::Settings;
use crate::database::controller::users::get_user_by_username;
use crate::database::controller::login_protection::{login_attempts_exceeded, login_attempts_usernames_exceeded, add_login_attempt, add_login_attempt_username};
use crate::database::controller::members::get_members_by_user_uuid;
use rocket::request::{FromRequest, Outcome};
use rocket::http::{Status, ContentType, Header};
use std::str::{FromStr, Utf8Error};
use rocket::http::uncased::Uncased;
use std::borrow::Cow;
use rocket::response::Responder;
use base64::{decode, DecodeError};
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct MatrixAuthRequest {
user: User,
}
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct MatrixAuthResponse {
auth: Auth,
}
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct User{
id: String,
password: String,
}
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct Profile{
display_name: String
}
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct Auth{
success: bool,
mxid: Option<String>,
profile: Option<Profile>,
}
/// Matrix integration
#[post("/_matrix-internal/identity/v1/check_credentials", format = "json", data="<auth>")]
pub fn matrix_check_credentials(
settings: &State<Settings>, auth: Json<MatrixAuthRequest>
) -> Json<MatrixAuthResponse> {
let id = auth.user.id.replace("@", "").replace(":drk.digital", "");
match get_user_by_username(id.clone(), &settings){
None => {
match login_attempts_usernames_exceeded(settings, id.clone()){
Ok(result) => {
if result{
return Json(MatrixAuthResponse {
auth: Auth{
success: false,
mxid: None,
profile: None
}
}
)
}else{
add_login_attempt_username(settings, id);
return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})
}
},
Err(_) => {
return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})
}
}
},
Some(user) => {
let user = match login_attempts_usernames_exceeded(settings, id.clone()){
Ok(result) => {
if result{
return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})
}else{
user
}
},
Err(_) => {
return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})
}
};
let password_hash = match user.password.clone(){
None => {return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})}
Some(pw) => pw
};
if argon2::verify_encoded(&password_hash, auth.user.password.as_ref()).unwrap() {
let member = match get_members_by_user_uuid(user.id, &settings).first(){
Some(member) => {
return Json(MatrixAuthResponse {auth: Auth{
success: true,
mxid: Some(format!("@{}:drk.digital", id)),
profile: Some(Profile{
display_name: format!("{} {}", member.firstname, member.lastname)
})
}})
},
None => return Json(MatrixAuthResponse {auth: Auth {
success: false,
mxid: None,
profile: None
}})
};
} else {
add_login_attempt_username(settings, id);
return Json(MatrixAuthResponse {auth: Auth{
success: false,
mxid: None,
profile: None
}})
}
}
}
}
#[derive(Debug)]
pub struct BasicAuth{
username: String,
password: String,
}
fn parse_auth_header(header : &str) -> Option<BasicAuth>{
let mut header = &header.replace("Basic ", "");
let header = match decode(header){
Ok(h) => h,
Err(e) => {
warn!("Couldn't parse authorization header (base64): {}, header: {}", e, header);
return None
}
};
let header = match std::str::from_utf8(&header){
Ok(header) => header,
Err(e) => {
warn!("Invalid UTF-8 sequence in authorization header: {}, header: {}", e, String::from_utf8_lossy(&header));
return None
}
};
let mut parts = header.split(":");
let username = match parts.next(){
Some(username) => username.to_string(),
None => return None
};
let password : String = parts.collect();
if password.len() > 0 {
Some(BasicAuth{
username,
password
})
}else{
None
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for BasicAuth{
type Error = ();
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let header: Vec<_> = request.headers().get("Authorization").collect();
if header.len() == 1{
match parse_auth_header(&header[0]){
Some(auth_parsed) => Outcome::Success(auth_parsed),
None => Outcome::Forward(())
}
}else{
Outcome::Forward(())
}
}
}
pub struct HttpBasicAuth{
status: Status
}
impl<'r> Responder<'r, 'static> for HttpBasicAuth {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build()
.raw_header("WWW-Authenticate", "Basic")
.status(self.status)
.ok()
}
}
#[get("/api/http_basic_auth", rank=2)]
pub fn http_basic_auth2() -> HttpBasicAuth{
HttpBasicAuth{
status: Status::Unauthorized
}
}
#[get("/api/http_basic_auth", rank=1)]
pub fn http_basic_auth(
settings: &State<Settings>, auth: BasicAuth
) -> HttpBasicAuth{
let mut response = HttpBasicAuth{
status: Status::Forbidden
};
let pwd = auth.password;
let username = auth.username;
warn!("user: {}, pwd: {}", username, pwd);
match get_user_by_username(username.clone(), &settings){
None => {
match login_attempts_usernames_exceeded(settings, username.clone()){
Ok(result) => {
if result{
return response;
}else{
add_login_attempt_username(settings, username.clone());
}
},
Err(_) => {}
}
},
Some(user) => {
let user = match login_attempts_usernames_exceeded(settings, username.clone()){
Ok(result) => {
if result{
return response;
}else{
user
}
},
Err(_) => {
return response;
}
};
let password_hash = match user.password.clone(){
None => {
return response
}
Some(pw) => pw
};
if argon2::verify_encoded(&password_hash, pwd.as_ref()).unwrap() {
response.status = Status::Ok;
} else {
add_login_attempt_username(settings, username);
}
}
};
response
}