From 8f7900759fdd2f2578461ac8aa38bbcd854a6b0f Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Thu, 20 Jan 2022 21:50:58 -0800 Subject: [PATCH] Fix `scope` and `refresh_token` for API key logins API key logins use a scope of `api`, not `api offline_access`. Since `offline_access` is not requested, no `refresh_token` is returned either. --- src/api/identity.rs | 27 +++++++++++++++------------ src/db/models/device.rs | 9 +++++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 49310930..0adc542f 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -61,13 +61,15 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { // Get device by refresh token let mut device = Device::find_by_refresh_token(&token, &conn).map_res("Invalid refresh token")?; - // COMMON + let scope = "api offline_access"; + let scope_vec = vec!["api".into(), "offline_access".into()]; + + // Common let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); let orgs = UserOrganization::find_confirmed_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, scope_vec); device.save(&conn)?; + Ok(Json(json!({ "access_token": access_token, "expires_in": expires_in, @@ -79,7 +81,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { "Kdf": user.client_kdf_type, "KdfIterations": user.client_kdf_iter, "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing - "scope": "api offline_access", + "scope": scope, "unofficialServer": true, }))) } @@ -90,6 +92,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult if scope != "api offline_access" { err!("Scope not supported") } + let scope_vec = vec!["api".into(), "offline_access".into()]; // Ratelimit the login crate::ratelimit::check_limit_login(&ip.ip)?; @@ -157,8 +160,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult // Common let orgs = UserOrganization::find_confirmed_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, scope_vec); device.save(&conn)?; let mut result = json!({ @@ -173,7 +175,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult "Kdf": user.client_kdf_type, "KdfIterations": user.client_kdf_iter, "ResetMasterPassword": false,// TODO: Same as above - "scope": "api offline_access", + "scope": scope, "unofficialServer": true, }); @@ -191,6 +193,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult if scope != "api" { err!("Scope not supported") } + let scope_vec = vec!["api".into()]; // Ratelimit the login crate::ratelimit::check_limit_login(&ip.ip)?; @@ -232,24 +235,24 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult // Common let orgs = UserOrganization::find_confirmed_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, scope_vec); device.save(&conn)?; info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip); + // Note: No refresh_token is returned. The CLI just repeats the + // client_credentials login flow when the existing token expires. Ok(Json(json!({ "access_token": access_token, "expires_in": expires_in, "token_type": "Bearer", - "refresh_token": device.refresh_token, "Key": user.akey, "PrivateKey": user.private_key, "Kdf": user.client_kdf_type, "KdfIterations": user.client_kdf_iter, "ResetMasterPassword": false, // TODO: Same as above - "scope": "api", + "scope": scope, "unofficialServer": true, }))) } diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 2fbdea01..05955c04 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -60,7 +60,12 @@ impl Device { self.twofactor_remember = None; } - pub fn refresh_tokens(&mut self, user: &super::User, orgs: Vec) -> (String, i64) { + pub fn refresh_tokens( + &mut self, + user: &super::User, + orgs: Vec, + scope: Vec, + ) -> (String, i64) { // If there is no refresh token, we create one if self.refresh_token.is_empty() { use crate::crypto; @@ -98,7 +103,7 @@ impl Device { sstamp: user.security_stamp.to_string(), device: self.uuid.to_string(), - scope: vec!["api".into(), "offline_access".into()], + scope, amr: vec!["Application".into()], };