269 lines
8.4 KiB
Rust
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
|
|
} |