-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement alternative login with device flow (#568)
When approving from other devices we need to support the master key flow. Also implements login with device for the CLI as a testing tool.
- Loading branch information
Showing
16 changed files
with
405 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
crates/bitwarden/src/auth/api/request/auth_request_token_request.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use log::debug; | ||
use serde::{Deserialize, Serialize}; | ||
use uuid::Uuid; | ||
|
||
use crate::{ | ||
auth::api::response::IdentityTokenResponse, | ||
client::{client_settings::DeviceType, ApiConfigurations}, | ||
error::Result, | ||
}; | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct AuthRequestTokenRequest { | ||
scope: String, | ||
client_id: String, | ||
#[serde(rename = "deviceType")] | ||
device_type: u8, | ||
#[serde(rename = "deviceIdentifier")] | ||
device_identifier: String, | ||
#[serde(rename = "deviceName")] | ||
device_name: String, | ||
grant_type: String, | ||
#[serde(rename = "username")] | ||
email: String, | ||
#[serde(rename = "authRequest")] | ||
auth_request_id: Uuid, | ||
#[serde(rename = "password")] | ||
access_code: String, | ||
} | ||
|
||
impl AuthRequestTokenRequest { | ||
pub fn new( | ||
email: &str, | ||
auth_request_id: &Uuid, | ||
access_code: &str, | ||
device_type: DeviceType, | ||
device_identifier: &str, | ||
) -> Self { | ||
let obj = Self { | ||
scope: "api offline_access".to_string(), | ||
client_id: "web".to_string(), | ||
device_type: device_type as u8, | ||
device_identifier: device_identifier.to_string(), | ||
device_name: "chrome".to_string(), | ||
grant_type: "password".to_string(), | ||
email: email.to_string(), | ||
auth_request_id: *auth_request_id, | ||
access_code: access_code.to_string(), | ||
}; | ||
debug!("initializing {:?}", obj); | ||
obj | ||
} | ||
|
||
pub(crate) async fn send( | ||
&self, | ||
configurations: &ApiConfigurations, | ||
) -> Result<IdentityTokenResponse> { | ||
super::send_identity_connect_request(configurations, Some(&self.email), &self).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
use std::num::NonZeroU32; | ||
|
||
use bitwarden_api_api::{ | ||
apis::auth_requests_api::{auth_requests_id_response_get, auth_requests_post}, | ||
models::{AuthRequestCreateRequestModel, AuthRequestType}, | ||
}; | ||
use bitwarden_crypto::Kdf; | ||
use uuid::Uuid; | ||
|
||
use crate::{ | ||
auth::{ | ||
api::{request::AuthRequestTokenRequest, response::IdentityTokenResponse}, | ||
auth_request::new_auth_request, | ||
}, | ||
client::{LoginMethod, UserLoginMethod}, | ||
error::Result, | ||
mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}, | ||
Client, | ||
}; | ||
|
||
pub struct NewAuthRequestResponse { | ||
pub fingerprint: String, | ||
email: String, | ||
device_identifier: String, | ||
auth_request_id: Uuid, | ||
access_code: String, | ||
private_key: String, | ||
} | ||
|
||
pub(crate) async fn send_new_auth_request( | ||
client: &mut Client, | ||
email: String, | ||
device_identifier: String, | ||
) -> Result<NewAuthRequestResponse> { | ||
let config = client.get_api_configurations().await; | ||
|
||
let auth = new_auth_request(&email)?; | ||
|
||
let req = AuthRequestCreateRequestModel { | ||
email: email.clone(), | ||
public_key: auth.public_key, | ||
device_identifier: device_identifier.clone(), | ||
access_code: auth.access_code.clone(), | ||
r#type: AuthRequestType::Variant0, // AuthenticateAndUnlock | ||
}; | ||
|
||
let res = auth_requests_post(&config.api, Some(req)).await?; | ||
|
||
Ok(NewAuthRequestResponse { | ||
fingerprint: auth.fingerprint, | ||
email, | ||
device_identifier, | ||
auth_request_id: res.id.unwrap(), | ||
access_code: auth.access_code, | ||
private_key: auth.private_key, | ||
}) | ||
} | ||
|
||
pub(crate) async fn complete_auth_request( | ||
client: &mut Client, | ||
auth_req: NewAuthRequestResponse, | ||
) -> Result<()> { | ||
let config = client.get_api_configurations().await; | ||
|
||
let res = auth_requests_id_response_get( | ||
&config.api, | ||
auth_req.auth_request_id, | ||
Some(&auth_req.access_code), | ||
) | ||
.await?; | ||
|
||
let approved = res.request_approved.unwrap_or(false); | ||
|
||
if !approved { | ||
return Err("Auth request was not approved".into()); | ||
} | ||
|
||
let response = AuthRequestTokenRequest::new( | ||
&auth_req.email, | ||
&auth_req.auth_request_id, | ||
&auth_req.access_code, | ||
config.device_type, | ||
&auth_req.device_identifier, | ||
) | ||
.send(config) | ||
.await?; | ||
|
||
if let IdentityTokenResponse::Authenticated(r) = response { | ||
let kdf = Kdf::PBKDF2 { | ||
iterations: NonZeroU32::new(600_000).unwrap(), | ||
}; | ||
|
||
client.set_tokens( | ||
r.access_token.clone(), | ||
r.refresh_token.clone(), | ||
r.expires_in, | ||
); | ||
client.set_login_method(LoginMethod::User(UserLoginMethod::Username { | ||
client_id: "web".to_owned(), | ||
email: auth_req.email.to_owned(), | ||
kdf: kdf.clone(), | ||
})); | ||
|
||
let method = match res.master_password_hash { | ||
Some(_) => AuthRequestMethod::MasterKey { | ||
protected_master_key: res.key.unwrap().parse().unwrap(), | ||
auth_request_key: r.key.unwrap().parse().unwrap(), | ||
}, | ||
None => AuthRequestMethod::UserKey { | ||
protected_user_key: res.key.unwrap().parse().unwrap(), | ||
}, | ||
}; | ||
|
||
client | ||
.crypto() | ||
.initialize_user_crypto(InitUserCryptoRequest { | ||
kdf_params: kdf, | ||
email: auth_req.email, | ||
private_key: r.private_key.unwrap(), | ||
method: InitUserCryptoMethod::AuthRequest { | ||
request_private_key: auth_req.private_key, | ||
method, | ||
}, | ||
}) | ||
.await?; | ||
|
||
Ok(()) | ||
} else { | ||
Err("Failed to authenticate".into()) | ||
} | ||
} |
Oops, something went wrong.