Skip to content

Auth/PM-22661 - Send Access #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 60 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
5cb35a9
PM-22661 - Add anonymous auth_client and documentation
JaredSnider-Bitwarden Jul 15, 2025
07e49b3
PM-22661 - WIP on building various classes
JaredSnider-Bitwarden Jul 17, 2025
bec03fc
PM-22661 - SendAccessTokenPayload serialization
JaredSnider-Bitwarden Jul 17, 2025
5a140fd
PM-22661 - Add traits for converting SendAccessTokenRequest to SendAcโ€ฆ
JaredSnider-Bitwarden Jul 17, 2025
68617bd
PM-22661 - WIP - Start defining send token api service + response modโ€ฆ
JaredSnider-Bitwarden Jul 17, 2025
6a5370f
PM-22661 - (1) Progress on SendApiService (2) Refactor names / folderโ€ฆ
JaredSnider-Bitwarden Jul 18, 2025
f5daadb
PM-22661 - Wasm Crate - add serde_urlencoded for quicker encoding of โ€ฆ
JaredSnider-Bitwarden Jul 18, 2025
dac6068
PM-22661 - SendTokenApiService - clean up comments
JaredSnider-Bitwarden Jul 18, 2025
c435f56
PM-22661 - SendTokenApiService - fix import
JaredSnider-Bitwarden Jul 18, 2025
e209646
PM-22661 - SendAccessTokenRequest - add comment
JaredSnider-Bitwarden Jul 18, 2025
d70bc5b
PM-22661 - SendAccess mod - remove enums
JaredSnider-Bitwarden Jul 18, 2025
cac6788
PM-22661 - SendTokenApiService integration test suite - some progress
JaredSnider-Bitwarden Jul 18, 2025
14243d9
PM-22661 - bw-wasm-internal crate cargo.toml - Instead of just builiโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
7c7f1cb
PM-22661 - AuthClient - must make public outside of crate as integratโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
8b57676
PM-22661 - Lib.rs - make auth module public
JaredSnider-Bitwarden Jul 21, 2025
ad9fd99
PM-22661 - Auth mod.rs - make send_access module public.
JaredSnider-Bitwarden Jul 21, 2025
14238ac
PM-22661 - SendAccessToken - add docs and make properties public
JaredSnider-Bitwarden Jul 21, 2025
1d7d638
PM-22661 - Auth>SendAccess>Requests mod.rs - add enums module and makโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
8674ef4
PM-22661 - Fix imports after creating responses/enums and requests/eโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
19a4de8
PM-22661 - SendTokenApiService - make auth_client public outside of cโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
33a1d86
PM-22661 - SendAccessCredentials - add serialization + unit tests for it
JaredSnider-Bitwarden Jul 21, 2025
a81ca5d
PM-22661 - SendAccessTokenRequest - add serialization
JaredSnider-Bitwarden Jul 21, 2025
c04c12f
PM-22661 - SendAccessTokenResponse - add serialization
JaredSnider-Bitwarden Jul 21, 2025
58ef896
PM-22661 - Port over Dani's changes to fix integration tests
JaredSnider-Bitwarden Jul 21, 2025
d91275a
PM-22661 - Add clone to request and credentials for easier test usage
JaredSnider-Bitwarden Jul 21, 2025
9327e04
PM-22661 - SendTokenApiService integration tests - get tests passing
JaredSnider-Bitwarden Jul 21, 2025
11b5ab6
PM-22661 - SendTokenApiService - remove unnecessary mut
JaredSnider-Bitwarden Jul 21, 2025
3fb485f
PM-22661 - Add a bunch of docs
JaredSnider-Bitwarden Jul 21, 2025
c0a0f9a
PM-22661 - Rename SendAccessTokenPayloadVariant to SendAccessTokenPayโ€ฆ
JaredSnider-Bitwarden Jul 21, 2025
44fb2f6
PM-22661 - More docs
JaredSnider-Bitwarden Jul 21, 2025
cc925bd
PM-22661 - SendAccessTokenError - add tests
JaredSnider-Bitwarden Jul 21, 2025
9c57d56
PM-22661 - (1) Add new, separate enums for invalid grant and request โ€ฆ
JaredSnider-Bitwarden Jul 22, 2025
b476eb6
Merge remote-tracking branch 'origin/main' into auth/pm-22661/send-acโ€ฆ
JaredSnider-Bitwarden Jul 23, 2025
34fd641
PM-22661 - Clean up comments
JaredSnider-Bitwarden Jul 23, 2025
acc3870
PM-22661 - SendTokenApiService - clean up typescript commented out coโ€ฆ
JaredSnider-Bitwarden Jul 23, 2025
5db40c1
PM-22661 - Move / refactor request logic into token_request in auth cโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
293d0d7
PM-22661 - Move common module over to auth crate
JaredSnider-Bitwarden Jul 24, 2025
1deb5fa
PM-22661 - Internal module - declare token_request_payload but limit โ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
fe238de
PM-22661 - SendAccess module - (1) declare internal module so send acโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
4bce01d
PM-22661 - TokenRequestPayload - (1) bring over and combine relevant โ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
c620e4f
PM-22661 - Delete files that were moved to bw-auth crate
JaredSnider-Bitwarden Jul 24, 2025
21842bf
PM-22661 - token_request --> access_token_request
JaredSnider-Bitwarden Jul 24, 2025
5bac04e
PM-22661 - Bring over access_token_response
JaredSnider-Bitwarden Jul 24, 2025
4b20cd0
PM-22661 - Bring over token_api_error_response content to bw-auth
JaredSnider-Bitwarden Jul 24, 2025
972a8b5
PM-22661 - Bring over SendAccessTokenResponse and rename in bw-auth
JaredSnider-Bitwarden Jul 24, 2025
c107751
PM-22661 - SendAccessTokenError renamed to SendAccessTokenApiErrorResโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
0551d35
PM-22661 - internal mod - rearrange code
JaredSnider-Bitwarden Jul 24, 2025
d60e6dd
PM-22661 - Re-add trait for converting api success response to send aโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
8f68dc3
PM-22661 - SendAccessTokenPayload --> SendAccessTokenRequestPayload
JaredSnider-Bitwarden Jul 24, 2025
2ba21a8
PM-22661 - Move request_send_access_token logic over and update bw-auโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
458d992
PM-22661 - Finish removing all changes but test file from wasm internโ€ฆ
JaredSnider-Bitwarden Jul 24, 2025
e7ea86f
PM-22661 - Apply Dani's fixes
JaredSnider-Bitwarden Jul 25, 2025
74b8fe3
PM-22661 - Readme add todo
JaredSnider-Bitwarden Aug 7, 2025
66fd4a0
PM-22661 - SendAccess Client - request_send_access_token - get error โ€ฆ
JaredSnider-Bitwarden Aug 7, 2025
804c87a
PM-22661 - SendAccessClient - request_send_access_token - actually coโ€ฆ
JaredSnider-Bitwarden Aug 7, 2025
00f417c
PM-22661 - spacing
JaredSnider-Bitwarden Aug 7, 2025
0270a22
PM-22661 - WIP on integration tests
JaredSnider-Bitwarden Aug 8, 2025
d692b6d
PM-22661 - Rename internal to just API so we can make response modelsโ€ฆ
JaredSnider-Bitwarden Aug 8, 2025
856ecb0
PM-22661 - Worked with Andreas to get modules structured better
JaredSnider-Bitwarden Aug 8, 2025
f771dbc
PM-22661 - Lint
JaredSnider-Bitwarden Aug 11, 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
6 changes: 6 additions & 0 deletions Cargo.lock

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

10 changes: 10 additions & 0 deletions crates/bitwarden-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,24 @@ wasm = [
[dependencies]
bitwarden-core = { workspace = true, features = ["internal"] }
bitwarden-error = { workspace = true }
chrono = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_urlencoded = "0.7.1"
serde_qs = { workspace = true }
thiserror = { workspace = true }
tsify = { workspace = true, optional = true }
uniffi = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }

[dev-dependencies]
tokio = { workspace = true, features = ["rt"] }
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
wiremock = "0.6.0"

[lints]
workspace = true


4 changes: 4 additions & 0 deletions crates/bitwarden-auth/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Bitwarden Auth

Contains the implementation of the auth functionality for the Bitwarden Password Manager.

# Send Access

- TODO: add context
10 changes: 10 additions & 0 deletions crates/bitwarden-auth/src/common/enums/grant_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};

/// Add a trait to this enum to allow for serialization and deserialization of the enum values.
#[derive(Serialize, Deserialize, Debug)]
/// Instructs deserialization to map the string "send_access" to the `SendAccess` variant.
#[serde(rename_all = "snake_case")]
pub enum GrantType {
SendAccess,
// TODO: Add other grant types as needed.
}
5 changes: 5 additions & 0 deletions crates/bitwarden-auth/src/common/enums/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod grant_type;
mod scope;

pub use grant_type::GrantType;
pub use scope::Scope;
8 changes: 8 additions & 0 deletions crates/bitwarden-auth/src/common/enums/scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub enum Scope {
#[serde(rename = "api.send")]
Send,
// TODO: Add other scopes as needed.
}
1 change: 1 addition & 0 deletions crates/bitwarden-auth/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod enums;
6 changes: 5 additions & 1 deletion crates/bitwarden-auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#![doc = include_str!("../README.md")]

mod auth_client;
mod send_access;
mod common;

/// Module for handling Send Access token requests and responses.
pub mod send_access;

pub use auth_client::{AuthClient, AuthClientExt};
pub use common::enums::{GrantType, Scope};
97 changes: 97 additions & 0 deletions crates/bitwarden-auth/src/send_access/access_token_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#[cfg(feature = "wasm")]
use tsify::Tsify;

/// Credentials for sending password secured access requests.
/// Clone auto implements the standard lib's Clone trait, allowing us to create copies of this
/// struct.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct SendPasswordCredentials {
/// A Base64-encoded hash of the password protecting the send.
pub password_hash_b64: String,
}

/// Credentials for sending an OTP to the user's email address.
/// This is used when the send requires email verification with an OTP.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct SendEmailCredentials {
/// The email address to which the OTP will be sent.
pub email: String,
}

/// Credentials for getting a send access token using an email and OTP.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct SendEmailOtpCredentials {
/// The email address to which the OTP will be sent.
pub email: String,
/// The one-time password (OTP) that the user has received via email.
pub otp: String,
}

/// The credentials used for send access requests.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
// Use untagged so that each variant can be serialized without a type tag.
// For example, this allows us to serialize the password credentials as just
// {"password_hash_b64": "value"} instead of {"type": "password", "password_hash_b64": "value"}.
#[serde(untagged)]
pub enum SendAccessCredentials {
#[allow(missing_docs)]
Password(SendPasswordCredentials),
#[allow(missing_docs)]
Email(SendEmailCredentials),
#[allow(missing_docs)]
EmailOtp(SendEmailOtpCredentials),
}

/// A request structure for requesting a send access token from the API.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub struct SendAccessTokenRequest {
/// The id of the send for which the access token is requested.
pub send_id: String,

/// The optional send access credentials.
pub send_access_credentials: Option<SendAccessCredentials>,
}

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

mod send_access_credentials_tests {
use serde_json;

use super::*;

#[test]
fn serialize_password_credentials() {
let creds = SendAccessCredentials::Password(SendPasswordCredentials {
password_hash_b64: "ha$h".into(),
});
let json = serde_json::to_string(&creds).unwrap();
assert_eq!(json, r#"{"password_hash_b64":"ha$h"}"#);
}

#[test]
fn serialize_email_credentials() {
let creds = SendAccessCredentials::Email(SendEmailCredentials {
email: "[email protected]".into(),
});
let json = serde_json::to_string(&creds).unwrap();
assert_eq!(json, r#"{"email":"[email protected]"}"#);
}

#[test]
fn serialize_email_otp_credentials() {
let creds = SendAccessCredentials::EmailOtp(SendEmailOtpCredentials {
email: "[email protected]".into(),
otp: "123456".into(),
});
let json = serde_json::to_string(&creds).unwrap();
assert_eq!(json, r#"{"email":"[email protected]","otp":"123456"}"#);
}
}
}
86 changes: 86 additions & 0 deletions crates/bitwarden-auth/src/send_access/access_token_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::fmt::Debug;

use crate::send_access::api::{SendAccessTokenApiErrorResponse, SendAccessTokenApiSuccessResponse};

/// A send access token which can be used to access a send.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
#[derive(Debug)]
pub struct SendAccessTokenResponse {
/// The actual token string.
pub token: String,
/// The timestamp in milliseconds when the token expires.
pub expires_at: i64,
}

impl From<SendAccessTokenApiSuccessResponse> for SendAccessTokenResponse {
fn from(response: SendAccessTokenApiSuccessResponse) -> Self {
// We want to convert the expires_in from seconds to a millisecond timestamp to have a
// concrete time the token will expire as it is easier to build logic around a
// concrete time rather than a duration.
let expires_at =
chrono::Utc::now().timestamp_millis() + (response.expires_in * 1000) as i64;

SendAccessTokenResponse {
token: response.access_token,
expires_at,
}
}
}

#[allow(missing_docs)]
// We're using the full variant of the bitwarden-error macro because we want to keep the contents of
// SendAccessTokenApiErrorResponse
#[bitwarden_error::bitwarden_error(full)]
#[derive(Debug, thiserror::Error)]
pub enum SendAccessTokenError {
#[error("API Error: {0:?}")]
Api(AuthApiError),

#[error("Send access token error response")]
Response(SendAccessTokenApiErrorResponse),
}

// This is just a utility function so that the ? operator works correctly without manual mapping
impl From<reqwest::Error> for SendAccessTokenError {
fn from(value: reqwest::Error) -> Self {
Self::Api(AuthApiError(value))
}
}

// This wrapper needs to exist because the `bitwarden_error(full)` macro requires every variant to
// implement serialize+tsify, which is not the case for the `Api` variant. We only really care about
// the contents of the `Response` variant, so ideally the macro would support a way of marking the
// `Api` variant somehow so it gets serialized as a plain string.
// As that is not the case, we have to implement it manually.

#[derive(Debug)]
pub struct AuthApiError(reqwest::Error);

#[cfg(feature = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
const TS_CUSTOM_TYPES: &'static str = r#"
export type AuthApiError = string;
"#;

impl serde::Serialize for AuthApiError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{:?}", self.0))
}
}

impl<'de> serde::Deserialize<'de> for AuthApiError {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("deserialization not supported"))
}
}
13 changes: 13 additions & 0 deletions crates/bitwarden-auth/src/send_access/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod token_api_error_response;
mod token_api_success_response;
mod token_request_payload;

pub use token_api_error_response::{
SendAccessTokenApiErrorResponse, SendAccessTokenInvalidGrantError,
SendAccessTokenInvalidRequestError,
};
pub use token_api_success_response::SendAccessTokenApiSuccessResponse;
// Keep payload types internal to the crate
pub(crate) use token_request_payload::{
SendAccessTokenPayloadCredentials, SendAccessTokenRequestPayload,
};
Loading
Loading