Updated IP logging to use client_ip, to match old remote behavior.
Improved error logging, now it won't show a generic error message in some situations. Removed delete device, which is not needed as it will be overwritten later. Logged more info when an error occurs saving a device. Added orgmanager to JWT claims.
This commit is contained in:
parent
19754c967f
commit
7adc045b80
|
@ -1,5 +1,3 @@
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
||||||
|
|
||||||
use rocket::request::LenientForm;
|
use rocket::request::LenientForm;
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
|
|
||||||
|
@ -15,6 +13,8 @@ use crate::util::{self, JsonMap};
|
||||||
|
|
||||||
use crate::api::{ApiResult, EmptyResult, JsonResult};
|
use crate::api::{ApiResult, EmptyResult, JsonResult};
|
||||||
|
|
||||||
|
use crate::auth::ClientIp;
|
||||||
|
|
||||||
use crate::CONFIG;
|
use crate::CONFIG;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
|
@ -22,13 +22,13 @@ pub fn routes() -> Vec<Route> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/connect/token", data = "<data>")]
|
#[post("/connect/token", data = "<data>")]
|
||||||
fn login(data: LenientForm<ConnectData>, conn: DbConn, socket: Option<SocketAddr>) -> JsonResult {
|
fn login(data: LenientForm<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult {
|
||||||
let data: ConnectData = data.into_inner();
|
let data: ConnectData = data.into_inner();
|
||||||
validate_data(&data)?;
|
validate_data(&data)?;
|
||||||
|
|
||||||
match data.grant_type {
|
match data.grant_type {
|
||||||
GrantType::refresh_token => _refresh_login(data, conn),
|
GrantType::refresh_token => _refresh_login(data, conn),
|
||||||
GrantType::password => _password_login(data, conn, socket),
|
GrantType::password => _password_login(data, conn, ip),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,17 +56,11 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult {
|
||||||
"Key": user.key,
|
"Key": user.key,
|
||||||
"PrivateKey": user.private_key,
|
"PrivateKey": user.private_key,
|
||||||
}))),
|
}))),
|
||||||
Err(_) => err!("Failed to add device to user"),
|
Err(e) => err!("Failed to add device to user", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _password_login(data: ConnectData, conn: DbConn, remote: Option<SocketAddr>) -> JsonResult {
|
fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult {
|
||||||
// Get the ip for error reporting
|
|
||||||
let ip = match remote {
|
|
||||||
Some(ip) => ip.ip(),
|
|
||||||
None => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate scope
|
// Validate scope
|
||||||
let scope = data.scope.as_ref().unwrap();
|
let scope = data.scope.as_ref().unwrap();
|
||||||
if scope != "api offline_access" {
|
if scope != "api offline_access" {
|
||||||
|
@ -79,7 +73,7 @@ fn _password_login(data: ConnectData, conn: DbConn, remote: Option<SocketAddr>)
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => err!(format!(
|
None => err!(format!(
|
||||||
"Username or password is incorrect. Try again. IP: {}. Username: {}.",
|
"Username or password is incorrect. Try again. IP: {}. Username: {}.",
|
||||||
ip, username
|
ip.ip, username
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,7 +82,7 @@ fn _password_login(data: ConnectData, conn: DbConn, remote: Option<SocketAddr>)
|
||||||
if !user.check_valid_password(password) {
|
if !user.check_valid_password(password) {
|
||||||
err!(format!(
|
err!(format!(
|
||||||
"Username or password is incorrect. Try again. IP: {}. Username: {}.",
|
"Username or password is incorrect. Try again. IP: {}. Username: {}.",
|
||||||
ip, username
|
ip.ip, username
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,20 +93,15 @@ fn _password_login(data: ConnectData, conn: DbConn, remote: Option<SocketAddr>)
|
||||||
// Find device or create new
|
// Find device or create new
|
||||||
let mut device = match Device::find_by_uuid(&device_id, &conn) {
|
let mut device = match Device::find_by_uuid(&device_id, &conn) {
|
||||||
Some(device) => {
|
Some(device) => {
|
||||||
// Check if valid device
|
// Check if owned device, and recreate if not
|
||||||
if device.user_uuid != user.uuid {
|
if device.user_uuid != user.uuid {
|
||||||
match device.delete(&conn) {
|
info!("Device exists but is owned by another user. The old device will be discarded");
|
||||||
Ok(()) => Device::new(device_id, user.uuid.clone(), device_name, device_type),
|
Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
||||||
Err(_) => err!("Tried to delete device not owned by user, but failed"),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
||||||
// Create new device
|
|
||||||
Device::new(device_id, user.uuid.clone(), device_name, device_type)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let twofactor_token = twofactor_auth(&user.uuid, &data.clone(), &mut device, &conn)?;
|
let twofactor_token = twofactor_auth(&user.uuid, &data.clone(), &mut device, &conn)?;
|
||||||
|
@ -122,8 +111,8 @@ fn _password_login(data: ConnectData, conn: DbConn, remote: Option<SocketAddr>)
|
||||||
let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
|
let orgs = UserOrganization::find_by_user(&user.uuid, &conn);
|
||||||
|
|
||||||
let (access_token, expires_in) = device.refresh_tokens(&user, orgs);
|
let (access_token, expires_in) = device.refresh_tokens(&user, orgs);
|
||||||
if device.save(&conn).is_err() {
|
if let Err(e) = device.save(&conn) {
|
||||||
err!("Failed to add device to user")
|
err!("Failed to add device to user", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = json!({
|
let mut result = json!({
|
||||||
|
|
39
src/auth.rs
39
src/auth.rs
|
@ -1,7 +1,6 @@
|
||||||
///
|
///
|
||||||
/// JWT Handling
|
/// JWT Handling
|
||||||
///
|
///
|
||||||
|
|
||||||
use crate::util::read_file;
|
use crate::util::read_file;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
|
|
||||||
|
@ -76,6 +75,7 @@ pub struct JWTClaims {
|
||||||
pub orgowner: Vec<String>,
|
pub orgowner: Vec<String>,
|
||||||
pub orgadmin: Vec<String>,
|
pub orgadmin: Vec<String>,
|
||||||
pub orguser: Vec<String>,
|
pub orguser: Vec<String>,
|
||||||
|
pub orgmanager: Vec<String>,
|
||||||
|
|
||||||
// user security_stamp
|
// user security_stamp
|
||||||
pub sstamp: String,
|
pub sstamp: String,
|
||||||
|
@ -90,7 +90,6 @@ pub struct JWTClaims {
|
||||||
///
|
///
|
||||||
/// Bearer token authentication
|
/// Bearer token authentication
|
||||||
///
|
///
|
||||||
|
|
||||||
use rocket::Outcome;
|
use rocket::Outcome;
|
||||||
use rocket::request::{self, Request, FromRequest};
|
use rocket::request::{self, Request, FromRequest};
|
||||||
|
|
||||||
|
@ -139,13 +138,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
|
||||||
|
|
||||||
// Get access_token
|
// Get access_token
|
||||||
let access_token: &str = match request.headers().get_one("Authorization") {
|
let access_token: &str = match request.headers().get_one("Authorization") {
|
||||||
Some(a) => {
|
Some(a) => match a.rsplit("Bearer ").next() {
|
||||||
match a.rsplit("Bearer ").next() {
|
|
||||||
Some(split) => split,
|
Some(split) => split,
|
||||||
None => err_handler!("No access token provided")
|
None => err_handler!("No access token provided"),
|
||||||
}
|
},
|
||||||
}
|
None => err_handler!("No access token provided"),
|
||||||
None => err_handler!("No access token provided")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check JWT token is valid and get device and user from it
|
// Check JWT token is valid and get device and user from it
|
||||||
|
@ -256,7 +253,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
|
||||||
Outcome::Failure(f) => Outcome::Failure(f),
|
Outcome::Failure(f) => Outcome::Failure(f),
|
||||||
Outcome::Success(headers) => {
|
Outcome::Success(headers) => {
|
||||||
if headers.org_user_type >= UserOrgType::Admin {
|
if headers.org_user_type >= UserOrgType::Admin {
|
||||||
Outcome::Success(Self{
|
Outcome::Success(Self {
|
||||||
host: headers.host,
|
host: headers.host,
|
||||||
device: headers.device,
|
device: headers.device,
|
||||||
user: headers.user,
|
user: headers.user,
|
||||||
|
@ -285,7 +282,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
|
||||||
Outcome::Failure(f) => Outcome::Failure(f),
|
Outcome::Failure(f) => Outcome::Failure(f),
|
||||||
Outcome::Success(headers) => {
|
Outcome::Success(headers) => {
|
||||||
if headers.org_user_type == UserOrgType::Owner {
|
if headers.org_user_type == UserOrgType::Owner {
|
||||||
Outcome::Success(Self{
|
Outcome::Success(Self {
|
||||||
host: headers.host,
|
host: headers.host,
|
||||||
device: headers.device,
|
device: headers.device,
|
||||||
user: headers.user,
|
user: headers.user,
|
||||||
|
@ -297,3 +294,25 @@ impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Client IP address detection
|
||||||
|
///
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
pub struct ClientIp {
|
||||||
|
pub ip: IpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for ClientIp {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let ip = match request.client_ip() {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => "0.0.0.0".parse().unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Outcome::Success(ClientIp { ip })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ impl Device {
|
||||||
let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect();
|
let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect();
|
||||||
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect();
|
let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect();
|
||||||
let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();
|
let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();
|
||||||
|
let orgmanager: Vec<_> = orgs.iter().filter(|o| o.type_ == 3).map(|o| o.org_uuid.clone()).collect();
|
||||||
|
|
||||||
|
|
||||||
// Create the JWT claims struct, to send to the client
|
// Create the JWT claims struct, to send to the client
|
||||||
|
@ -92,6 +93,7 @@ impl Device {
|
||||||
orgowner,
|
orgowner,
|
||||||
orgadmin,
|
orgadmin,
|
||||||
orguser,
|
orguser,
|
||||||
|
orgmanager,
|
||||||
|
|
||||||
sstamp: user.security_stamp.to_string(),
|
sstamp: user.security_stamp.to_string(),
|
||||||
device: self.uuid.to_string(),
|
device: self.uuid.to_string(),
|
||||||
|
|
32
src/util.rs
32
src/util.rs
|
@ -2,22 +2,32 @@
|
||||||
/// Macros
|
/// Macros
|
||||||
///
|
///
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err {
|
macro_rules! _err_object {
|
||||||
($err:expr, $msg:expr) => {{
|
($msg:expr) => {{
|
||||||
error!("{}", $msg);
|
|
||||||
err_json!(json!({
|
err_json!(json!({
|
||||||
"error": $err,
|
"Message": "",
|
||||||
"error_description": $err,
|
"error": "",
|
||||||
|
"error_description": "",
|
||||||
|
"ValidationErrors": {"": [ $msg ]},
|
||||||
"ErrorModel": {
|
"ErrorModel": {
|
||||||
"Message": $msg,
|
"Message": $msg,
|
||||||
"ValidationErrors": null,
|
|
||||||
"ExceptionMessage": null,
|
|
||||||
"ExceptionStackTrace": null,
|
|
||||||
"InnerExceptionMessage": null,
|
|
||||||
"Object": "error"
|
"Object": "error"
|
||||||
}}))
|
},
|
||||||
|
"Object": "error"
|
||||||
|
}))
|
||||||
}};
|
}};
|
||||||
($msg:expr) => { err!("unknown_error", $msg) }
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! err {
|
||||||
|
($msg:expr) => {{
|
||||||
|
error!("{}", $msg);
|
||||||
|
_err_object!($msg)
|
||||||
|
}};
|
||||||
|
($usr_msg:expr, $log_value:expr) => {{
|
||||||
|
error!("{}: {:#?}", $usr_msg, $log_value);
|
||||||
|
_err_object!($usr_msg)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
Loading…
Reference in New Issue