Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4e31b98
PM-14922 - Identity Client - Offer get_password_prelogin_data as top โ€ฆ
JaredSnider-Bitwarden Nov 7, 2025
4991a67
PM-14922 - (1) create password_login feature folder (2) create passwoโ€ฆ
JaredSnider-Bitwarden Nov 7, 2025
fb0c1d9
PM-14922 - Rename password_prelogin to prelogin_password
JaredSnider-Bitwarden Nov 10, 2025
0d5daed
PM-14922 - Add api folder + request & response module stubs to set paโ€ฆ
JaredSnider-Bitwarden Nov 11, 2025
1fd065d
PM-14922 - commit draft of api req / response folder structure
JaredSnider-Bitwarden Nov 11, 2025
f3c8332
PM-14922 - PreloginPassword - add some comments
JaredSnider-Bitwarden Nov 11, 2025
c052c1b
PM-14922 - Add serde_repr
JaredSnider-Bitwarden Nov 12, 2025
f67b725
PM-14922 - Rename client to identity client
JaredSnider-Bitwarden Nov 12, 2025
41c17ae
PM-14922 - Copy over 2FA provider enum to api/enums for now
JaredSnider-Bitwarden Nov 13, 2025
6070fb3
PM-14922 - Add password grant type
JaredSnider-Bitwarden Nov 13, 2025
0bc2cff
PM-14922 - Identity client rename mod cleanup
JaredSnider-Bitwarden Nov 13, 2025
433bbfc
PM-14922 - Rename api to api_models
JaredSnider-Bitwarden Nov 13, 2025
a2ea7a8
PM-14922 - WIP on rest of stuff
JaredSnider-Bitwarden Nov 13, 2025
fe988a2
PM-14922 - Document the intention behind the models mod
JaredSnider-Bitwarden Nov 13, 2025
22fcdd6
PM-14922 - (1) Move to api_ prefixed request models instead of payloaโ€ฆ
JaredSnider-Bitwarden Nov 13, 2025
65b8c8f
PM-14922 - formatting
JaredSnider-Bitwarden Nov 13, 2025
cf72a49
PM-14922 misc cleanup
JaredSnider-Bitwarden Nov 14, 2025
7ed9bd4
PM-14922 - KM - adjust accessibility to allow MasterPasswordAuthenticโ€ฆ
JaredSnider-Bitwarden Nov 14, 2025
ddce26e
PM-14922 - (1) UserTokenApiRequest - make props public (2) Scope - Adโ€ฆ
JaredSnider-Bitwarden Nov 14, 2025
a54709e
PM-14922 - LoginViaPassword - wire up from to go from password login โ€ฆ
JaredSnider-Bitwarden Nov 14, 2025
e780140
PM-14922 - BW-auth crate - cargo.toml - add serde_json
JaredSnider-Bitwarden Nov 18, 2025
0db8985
PM-14922 - LoginDeviceRequest - Add docs about using device_type
JaredSnider-Bitwarden Nov 18, 2025
3c82f52
PM-14922 - WIP on Password login
JaredSnider-Bitwarden Nov 18, 2025
5089783
PM-14922 - Improve scope docs
JaredSnider-Bitwarden Nov 19, 2025
1248842
PM-14922 - Make login_via_password call send_login_request directly
JaredSnider-Bitwarden Nov 19, 2025
bed7fd6
PM-14922 - Clean up UserLoginApiRequest of unused imports
JaredSnider-Bitwarden Nov 19, 2025
a96f101
PM-14922 - Improve send_login_request
JaredSnider-Bitwarden Nov 19, 2025
aa66ebd
PM-14922 - improve docs
JaredSnider-Bitwarden Nov 19, 2025
e38787a
PM-14922 - improve send_login_request
JaredSnider-Bitwarden Nov 19, 2025
a46c36e
PM-14922 - Rename UserLoginApiRequest to just LoginApiRequest as thaโ€ฆ
JaredSnider-Bitwarden Nov 19, 2025
881aa5b
PM-14922 - Add DevicePushTokenSupport
JaredSnider-Bitwarden Nov 20, 2025
056615f
PM-14922 - WIP on LoginApiSuccess
JaredSnider-Bitwarden Nov 20, 2025
2a56838
PM-14922 - LoginApiSuccess - more fleshed out and building
JaredSnider-Bitwarden Nov 20, 2025
e5967f2
PM-14922 - more WIP on login via password success and error models
JaredSnider-Bitwarden Nov 20, 2025
2c409e1
PM-14922 - (1) Rename api_models to just api (2) Move common send_logโ€ฆ
JaredSnider-Bitwarden Nov 20, 2025
fec2ed3
PM-14922 - WIP on documenting login_success_api_response and figurinโ€ฆ
JaredSnider-Bitwarden Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions crates/bitwarden-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,29 @@ wasm = [
"bitwarden-core/wasm",
"dep:tsify",
"dep:wasm-bindgen",
"dep:wasm-bindgen-futures"
"dep:wasm-bindgen-futures",
] # WASM support

# Note: dependencies must be alphabetized to pass the cargo sort check in the CI pipeline.
[dependencies]
bitwarden-api-api = { workspace = true }
bitwarden-api-identity = { workspace = true }
bitwarden-core = { workspace = true, features = ["internal"] }
bitwarden-crypto = { workspace = true }
bitwarden-error = { workspace = true }
chrono = { workspace = true }
reqwest = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_repr = { workspace = true }
thiserror = { workspace = true }
tsify = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }

[dev-dependencies]
bitwarden-test = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["rt"] }
wiremock = "0.6.0"

Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden-auth/src/api/enums/grant_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ pub(crate) enum GrantType {
/// Bitwarden user.
SendAccess,
// TODO: Add other grant types as needed.
Password,
}
4 changes: 3 additions & 1 deletion crates/bitwarden-auth/src/api/enums/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

mod grant_type;
mod scope;
mod two_factor_provider;

pub(crate) use grant_type::GrantType;
pub(crate) use scope::Scope;
pub(crate) use scope::{Scope, scopes_to_string};
pub(crate) use two_factor_provider::TwoFactorProvider;
29 changes: 27 additions & 2 deletions crates/bitwarden-auth/src/api/enums/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,35 @@ use serde::{Deserialize, Serialize};
/// Scopes define the specific permissions an access token grants to the client.
/// They are requested by the client during token acquisition and enforced by the
/// resource server when the token is used.
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Scope {
/// The scope for accessing the Bitwarden API as a Bitwarden user.
#[serde(rename = "api")]
Api,
/// The scope for obtaining Bitwarden user scoped refresh tokens that allow offline access.
#[serde(rename = "offline_access")]
OfflineAccess,
/// The scope for accessing send resources outside the context of a Bitwarden user.
#[serde(rename = "api.send.access")]
ApiSendAccess,
// TODO: Add other scopes as needed.
}

impl Scope {
/// Returns the string representation of the scope as used in OAuth 2.0 requests.
pub(crate) fn as_str(&self) -> &'static str {
match self {
Scope::Api => "api",
Scope::OfflineAccess => "offline_access",
Scope::ApiSendAccess => "api.send.access",
}
}
}

/// Converts a slice of scopes into a space-separated string suitable for OAuth 2.0 requests.
pub(crate) fn scopes_to_string(scopes: &[Scope]) -> String {
scopes
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(" ")
}
20 changes: 20 additions & 0 deletions crates/bitwarden-auth/src/api/enums/two_factor_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use schemars::JsonSchema;
use serde_repr::{Deserialize_repr, Serialize_repr};

// TODO: this isn't likely to be only limited to API usage... so maybe move to a more general
// location?

/// Represents the two-factor authentication providers supported by Bitwarden.
#[allow(missing_docs)]
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, JsonSchema, Clone)]
#[repr(u8)]
pub enum TwoFactorProvider {
Authenticator = 0,
Email = 1,
Duo = 2,
Yubikey = 3,
U2f = 4,
Remember = 5,
OrganizationDuo = 6,
WebAuthn = 7,
}
4 changes: 4 additions & 0 deletions crates/bitwarden-auth/src/api/request/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Request models for Identity API endpoints that cannot be auto-generated
//! (e.g., connect/token endpoints) and are shared across multiple clients.
//!
//! For standard controller endpoints, use the `bitwarden-api-identity` crate.
4 changes: 4 additions & 0 deletions crates/bitwarden-auth/src/api/response/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Response models for Identity API endpoints that cannot be auto-generated
//! (e.g., connect/token endpoint) and are shared across multiple clients.
//!
//! For standard controller endpoints, use the `bitwarden-api-identity` crate.
54 changes: 54 additions & 0 deletions crates/bitwarden-auth/src/identity/api/login_request_header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use bitwarden_core::DeviceType;

/// Custom headers used in login requests to the connect/token endpoint
/// - distinct from standard HTTP headers available in `reqwest::header`.
#[derive(Debug, Clone)]
pub enum LoginRequestHeader {
/// The "Device-Type" header indicates the type of device making the request.
DeviceType(DeviceType),
}

impl LoginRequestHeader {
/// Returns the header name as a string.
pub fn header_name(&self) -> &'static str {
match self {
Self::DeviceType(_) => "Device-Type",
}
}

/// Returns the header value as a string.
pub fn header_value(&self) -> String {
match self {
Self::DeviceType(device_type) => (*device_type as u8).to_string(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_device_type_header_name() {
let header = LoginRequestHeader::DeviceType(DeviceType::SDK);
assert_eq!(header.header_name(), "Device-Type");
}

#[test]
fn test_device_type_header_value() {
let header = LoginRequestHeader::DeviceType(DeviceType::SDK);
assert_eq!(header.header_value(), "21");
}

#[test]
fn test_device_type_header_value_android() {
let header = LoginRequestHeader::DeviceType(DeviceType::Android);
assert_eq!(header.header_value(), "0");
}

#[test]
fn test_device_type_header_value_mac_os_cli() {
let header = LoginRequestHeader::DeviceType(DeviceType::MacOsCLI);
assert_eq!(header.header_value(), "24");
}
}
8 changes: 8 additions & 0 deletions crates/bitwarden-auth/src/identity/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! API related modules for Identity endpoints
pub(crate) mod login_request_header;
pub(crate) mod request;
pub(crate) mod response;

/// Common send function for login requests
mod send_login_request;
pub(crate) use send_login_request::send_login_request;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::fmt::Debug;

use bitwarden_core::DeviceType;
use serde::{Deserialize, Serialize, de::DeserializeOwned};

use crate::api::enums::{GrantType, Scope, TwoFactorProvider, scopes_to_string};

/// Standard scopes for user token requests: "api offline_access"
pub(crate) const STANDARD_USER_SCOPES: &[Scope] = &[Scope::Api, Scope::OfflineAccess];

/// The common payload properties to send to the /connect/token endpoint to obtain
/// tokens for a BW user.
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "T: Serialize + DeserializeOwned + Debug")] // Ensure T meets trait bounds
pub(crate) struct LoginApiRequest<T: Serialize + DeserializeOwned + Debug> {
// Standard OAuth2 fields
/// The client ID for the SDK consuming client.
/// Note: snake_case is intentional to match the API expectations.
pub client_id: String,

/// The grant type for the token request.
/// Note: snake_case is intentional to match the API expectations.
pub grant_type: GrantType,

/// The space-separated scopes for the token request (e.g., "api offline_access").
pub scope: String,

// Custom fields BW uses for user token requests
/// The device type making the request.
#[serde(rename = "deviceType")]
pub device_type: DeviceType,

/// The identifier of the device.
#[serde(rename = "deviceIdentifier")]
pub device_identifier: String,

/// The name of the device.
#[serde(rename = "deviceName")]
pub device_name: String,

/// The push notification registration token for mobile devices.
#[serde(rename = "devicePushToken")]
pub device_push_token: Option<String>,

// Two-factor authentication fields
/// The two-factor authentication token.
#[serde(rename = "twoFactorToken")]
pub two_factor_token: Option<String>,

/// The two-factor authentication provider.
#[serde(rename = "twoFactorProvider")]
pub two_factor_provider: Option<TwoFactorProvider>,

/// Whether to remember two-factor authentication on this device.
#[serde(rename = "twoFactorRemember")]
pub two_factor_remember: Option<bool>,

// Specific login mechanism fields will go here (e.g., password, SSO, etc)
#[serde(flatten)]
pub login_mechanism_fields: T,
}

impl<T: Serialize + DeserializeOwned + Debug> LoginApiRequest<T> {
/// Creates a new UserLoginApiRequest with standard scopes ("api offline_access").
/// The scope can be overridden after construction if needed for specific auth flows.
pub(crate) fn new(
client_id: String,
grant_type: GrantType,
device_type: DeviceType,
device_identifier: String,
device_name: String,
device_push_token: Option<String>,
login_mechanism_fields: T,
) -> Self {
Self {
client_id,
grant_type,
scope: scopes_to_string(STANDARD_USER_SCOPES),
device_type,
device_identifier,
device_name,
device_push_token,
two_factor_token: None,
two_factor_provider: None,
two_factor_remember: None,
login_mechanism_fields,
}
}
}
7 changes: 7 additions & 0 deletions crates/bitwarden-auth/src/identity/api/request/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Request models for Identity API endpoints that cannot be auto-generated
//! (e.g., connect/token endpoints) and are shared across multiple features within the identity
//! client
//!
//! For standard controller endpoints, use the `bitwarden-api-identity` crate.
mod login_api_request;
pub(crate) use login_api_request::LoginApiRequest;
Loading
Loading