@ -14,7 +14,7 @@ use crate::{
core ::two_factor ::{ duo , email , email ::EmailTokenData , yubikey } ,
core ::two_factor ::{ duo , email , email ::EmailTokenData , yubikey } ,
ApiResult , EmptyResult , JsonResult , JsonUpcase ,
ApiResult , EmptyResult , JsonResult , JsonUpcase ,
} ,
} ,
auth ::{ ClientHeaders, ClientIp } ,
auth ::{ generate_organization_api_key_login_claims, ClientHeaders, ClientIp } ,
db ::{ models ::* , DbConn } ,
db ::{ models ::* , DbConn } ,
error ::MapResult ,
error ::MapResult ,
mail , util , CONFIG ,
mail , util , CONFIG ,
@ -276,16 +276,23 @@ async fn _api_key_login(
conn : & mut DbConn ,
conn : & mut DbConn ,
ip : & ClientIp ,
ip : & ClientIp ,
) -> JsonResult {
) -> JsonResult {
// Validate scope
let scope = data . scope . as_ref ( ) . unwrap ( ) ;
if scope ! = "api" {
err ! ( "Scope not supported" )
}
let scope_vec = vec! [ "api" . into ( ) ] ;
// Ratelimit the login
// Ratelimit the login
crate ::ratelimit ::check_limit_login ( & ip . ip ) ? ;
crate ::ratelimit ::check_limit_login ( & ip . ip ) ? ;
// Validate scope
match data . scope . as_ref ( ) . unwrap ( ) . as_ref ( ) {
"api" = > _user_api_key_login ( data , user_uuid , conn , ip ) . await ,
"api.organization" = > _organization_api_key_login ( data , conn , ip ) . await ,
_ = > err ! ( "Scope not supported" ) ,
}
}
async fn _user_api_key_login (
data : ConnectData ,
user_uuid : & mut Option < String > ,
conn : & mut DbConn ,
ip : & ClientIp ,
) -> JsonResult {
// Get the user via the client_id
// Get the user via the client_id
let client_id = data . client_id . as_ref ( ) . unwrap ( ) ;
let client_id = data . client_id . as_ref ( ) . unwrap ( ) ;
let client_user_uuid = match client_id . strip_prefix ( "user." ) {
let client_user_uuid = match client_id . strip_prefix ( "user." ) {
@ -342,6 +349,7 @@ async fn _api_key_login(
}
}
// Common
// Common
let scope_vec = vec! [ "api" . into ( ) ] ;
let orgs = UserOrganization ::find_confirmed_by_user ( & user . uuid , conn ) . await ;
let orgs = UserOrganization ::find_confirmed_by_user ( & user . uuid , conn ) . await ;
let ( access_token , expires_in ) = device . refresh_tokens ( & user , orgs , scope_vec ) ;
let ( access_token , expires_in ) = device . refresh_tokens ( & user , orgs , scope_vec ) ;
device . save ( conn ) . await ? ;
device . save ( conn ) . await ? ;
@ -362,13 +370,43 @@ async fn _api_key_login(
"KdfMemory" : user . client_kdf_memory ,
"KdfMemory" : user . client_kdf_memory ,
"KdfParallelism" : user . client_kdf_parallelism ,
"KdfParallelism" : user . client_kdf_parallelism ,
"ResetMasterPassword" : false , // TODO: Same as above
"ResetMasterPassword" : false , // TODO: Same as above
"scope" : scope ,
"scope" : "api" ,
"unofficialServer" : true ,
"unofficialServer" : true ,
} ) ;
} ) ;
Ok ( Json ( result ) )
Ok ( Json ( result ) )
}
}
async fn _organization_api_key_login ( data : ConnectData , conn : & mut DbConn , ip : & ClientIp ) -> JsonResult {
// Get the org via the client_id
let client_id = data . client_id . as_ref ( ) . unwrap ( ) ;
let org_uuid = match client_id . strip_prefix ( "organization." ) {
Some ( uuid ) = > uuid ,
None = > err ! ( "Malformed client_id" , format! ( "IP: {}." , ip . ip ) ) ,
} ;
let org_api_key = match OrganizationApiKey ::find_by_org_uuid ( org_uuid , conn ) . await {
Some ( org_api_key ) = > org_api_key ,
None = > err ! ( "Invalid client_id" , format! ( "IP: {}." , ip . ip ) ) ,
} ;
// Check API key.
let client_secret = data . client_secret . as_ref ( ) . unwrap ( ) ;
if ! org_api_key . check_valid_api_key ( client_secret ) {
err ! ( "Incorrect client_secret" , format! ( "IP: {}. Organization: {}." , ip . ip , org_api_key . org_uuid ) )
}
let claim = generate_organization_api_key_login_claims ( org_api_key . uuid , org_api_key . org_uuid ) ;
let access_token = crate ::auth ::encode_jwt ( & claim ) ;
Ok ( Json ( json ! ( {
"access_token" : access_token ,
"expires_in" : 3600 ,
"token_type" : "Bearer" ,
"scope" : "api.organization" ,
"unofficialServer" : true ,
} ) ) )
}
/// Retrieves an existing device or creates a new device from ConnectData and the User
/// Retrieves an existing device or creates a new device from ConnectData and the User
async fn get_device ( data : & ConnectData , conn : & mut DbConn , user : & User ) -> ( Device , bool ) {
async fn get_device ( data : & ConnectData , conn : & mut DbConn , user : & User ) -> ( Device , bool ) {
// On iOS, device_type sends "iOS", on others it sends a number
// On iOS, device_type sends "iOS", on others it sends a number