Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 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
4df558f
PM-22661 - Change Scope to match new server value
JaredSnider-Bitwarden Aug 11, 2025
21e5fcd
PM-22661 - Add scope docs
JaredSnider-Bitwarden Aug 11, 2025
9cc4fb5
PM-22661 - Clean up unused import
JaredSnider-Bitwarden Aug 11, 2025
fe50102
PM-22661 - GrantType docs
JaredSnider-Bitwarden Aug 11, 2025
0612e4d
PM-22661 - SendAccessClient docs
JaredSnider-Bitwarden Aug 11, 2025
180f8d9
PM-22661 - format
JaredSnider-Bitwarden Aug 11, 2025
dd1db6b
PM-22661 - Address warnings in test
JaredSnider-Bitwarden Aug 11, 2025
dc2529d
PM-22661 - Add invalid send_id error case
JaredSnider-Bitwarden Aug 11, 2025
43e5b93
PM-22661 - SendAccess tests - finish adding invalid request scenarios
JaredSnider-Bitwarden Aug 11, 2025
7fa3ab1
PM-22661 - Get all send access tests passing and finish adding invaliโ€ฆ
JaredSnider-Bitwarden Aug 12, 2025
e927128
PM-22661 - Fix lint - alphabetize bw-auth crate deps
JaredSnider-Bitwarden Aug 12, 2025
c897dc0
PM-22661 - Actually sort bw-auth crate toml
JaredSnider-Bitwarden Aug 12, 2025
da447ef
Merge remote-tracking branch 'origin/main' into auth/pm-22661/send-acโ€ฆ
JaredSnider-Bitwarden Aug 12, 2025
98f5d7b
PM-22661 - Try to appease the linter again with turning links into acโ€ฆ
JaredSnider-Bitwarden Aug 12, 2025
be733db
PM-22661 - SendAccessTokenResponse - rename fields to camelCase
JaredSnider-Bitwarden Aug 12, 2025
b73c32c
PM-22661 - SendAccessTokenRequest - rename to camel case for typescript
JaredSnider-Bitwarden Aug 13, 2025
2e232cf
PM-22661 - Update invalid request / grant enums to use alias so we caโ€ฆ
JaredSnider-Bitwarden Aug 13, 2025
2c1b0e3
PM-22661 - Refactor to offer expected and unexpected errors + refactoโ€ฆ
JaredSnider-Bitwarden Aug 13, 2025
d65e7ee
PM-22661 - replace final usage of AuthApiError with IdentityTransportโ€ฆ
JaredSnider-Bitwarden Aug 13, 2025
8d0c65b
PM-22661 - SendAccessTokenError - use lowercase for expected / unexpeโ€ฆ
JaredSnider-Bitwarden Aug 14, 2025
d61c4ee
PM-22661 - Update SendAccessTokenApiErrorResponse to use a struct witโ€ฆ
JaredSnider-Bitwarden Aug 14, 2025
50010d2
PM-22661 - Try to fix lint errors
JaredSnider-Bitwarden Aug 14, 2025
f3d836d
PM-22661 - fix lint again
JaredSnider-Bitwarden Aug 14, 2025
10d23a8
PM-22661 - SendAccessTokenRequest - make send_access_credentials optiโ€ฆ
JaredSnider-Bitwarden Aug 14, 2025
814f3e0
PM-22661 - SendPasswordCredentials - camelCase output
JaredSnider-Bitwarden Aug 14, 2025
d42ddc2
PM-22661 - Go back to camel case for JSON serialization across board โ€ฆ
JaredSnider-Bitwarden Aug 19, 2025
f92a5c4
PM-22661 - cargo +nightly-2025-08-18 fmt
JaredSnider-Bitwarden Aug 19, 2025
0c3ebac
PM-22661 - Completely refactor SendAccessTokenApiErrorResponse to proโ€ฆ
JaredSnider-Bitwarden Aug 19, 2025
0f78bd1
PM-22661 - cargo format + lint
JaredSnider-Bitwarden Aug 20, 2025
9bff39f
PM-22661 - Get integration tests passing in light of previous token_aโ€ฆ
JaredSnider-Bitwarden Aug 20, 2025
fdcaba4
PM-22661 - TokenRequestPayload - fix comments
JaredSnider-Bitwarden Aug 21, 2025
01394b2
Merge remote-tracking branch 'origin/main' into auth/pm-22661/send-acโ€ฆ
JaredSnider-Bitwarden Aug 21, 2025
3fbdfe2
merge main and fix conflicts
JaredSnider-Bitwarden Aug 22, 2025
0c855e5
PM-22661 - Add docs
JaredSnider-Bitwarden Aug 22, 2025
b3b136e
PM-22661 - cargo lock changes after merge mistakes
JaredSnider-Bitwarden Aug 22, 2025
888ed38
PM-22661 - Rename IdentityTransportError to UnexpectedIdentityError
JaredSnider-Bitwarden Aug 22, 2025
bf85519
PM-22661 - cargo fmt
JaredSnider-Bitwarden Aug 22, 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.

11 changes: 11 additions & 0 deletions crates/bitwarden-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,28 @@ wasm = [
"dep:wasm-bindgen-futures"
] # WASM support

# Note: dependencies must be alphabetized to pass the cargo sort check in the CI pipeline..
[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_qs = { workspace = true }
serde_urlencoded = "0.7.1"
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"] }
wiremock = "0.6.0"
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }

[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
16 changes: 16 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,16 @@
use serde::{Deserialize, Serialize};

/// Represents the OAuth 2.0 grant types recognized by the Bitwarden API.
/// A grant type specifies the method a client uses to obtain an access token,
/// as defined in [RFC 6749, Section 4](https://datatracker.ietf.org/doc/html/rfc6749#section-4)
/// or by custom Bitwarden extensions. The value is sent in the `grant_type` parameter
/// of a token request.
#[derive(Serialize, Deserialize, Debug)]
/// Instructs deserialization to map the string "send_access" to the `SendAccess` variant.
#[serde(rename_all = "snake_case")]
pub enum GrantType {
/// A custom extension grant type for requesting send access tokens outside the context of a
/// Bitwarden user.
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;
13 changes: 13 additions & 0 deletions crates/bitwarden-auth/src/common/enums/scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};

/// The OAuth 2.0 scopes recognized by the Bitwarden API.
/// 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)]
pub enum Scope {
/// 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.
}
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};
251 changes: 251 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,251 @@
#[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, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[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, Debug)]
#[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, Debug)]
#[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, Debug)]
#[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, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[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,

#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "wasm", tsify(optional))]
/// The optional send access credentials.
pub send_access_credentials: Option<SendAccessCredentials>,
}

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

mod send_access_token_request_tests {
use serde_json::{from_str, to_string};

use super::*;

#[test]
fn deserialize_camelcase_request() {
let json = r#"
{
"sendId": "abc123",
"sendAccessCredentials": { "passwordHashB64": "ha$h" }
}"#;

let req: SendAccessTokenRequest = from_str(json).unwrap();
assert_eq!(req.send_id, "abc123");

let creds = req.send_access_credentials.expect("expected Some");
match creds {
SendAccessCredentials::Password(p) => assert_eq!(p.password_hash_b64, "ha$h"),
_ => panic!("expected Password variant"),
}
}

#[test]
fn serialize_camelcase_request_with_credentials() {
let req = SendAccessTokenRequest {
send_id: "abc123".into(),
send_access_credentials: Some(SendAccessCredentials::Password(
SendPasswordCredentials {
password_hash_b64: "ha$h".into(),
},
)),
};
let json = to_string(&req).unwrap();
assert_eq!(
json,
r#"{"sendId":"abc123","sendAccessCredentials":{"passwordHashB64":"ha$h"}}"#
);
}

#[test]
fn serialize_omits_optional_credentials_when_none() {
let req = SendAccessTokenRequest {
send_id: "abc123".into(),
send_access_credentials: None,
};
let json = to_string(&req).unwrap();
assert_eq!(json, r#"{"sendId":"abc123"}"#);
}

#[test]
fn roundtrip_camel_in_to_camel_out() {
let in_json = r#"
{
"sendId": "abc123",
"sendAccessCredentials": { "passwordHashB64": "ha$h" }
}"#;

let req: SendAccessTokenRequest = from_str(in_json).unwrap();
let out_json = to_string(&req).unwrap();
assert_eq!(
out_json,
r#"{"sendId":"abc123","sendAccessCredentials":{"passwordHashB64":"ha$h"}}"#
);
}

#[test]
fn snakecase_top_level_keys_are_rejected() {
let json = r#"
{
"send_id": "abc123",
"sendAccessCredentials": { "passwordHashB64": "ha$h" }
}"#;
let err = from_str::<SendAccessTokenRequest>(json).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("unknown field") && msg.contains("send_id"),
"unexpected: {msg}"
);
}

#[test]
fn extra_top_level_key_is_rejected() {
let json = r#"
{
"sendId": "abc123",
"sendAccessCredentials": { "passwordHashB64": "ha$h" },
"extra": "nope"
}"#;
let err = from_str::<SendAccessTokenRequest>(json).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("unknown field") && msg.contains("extra"),
"unexpected: {msg}"
);
}

#[test]
fn snakecase_nested_keys_are_rejected() {
let json = r#"
{
"sendId": "abc123",
"sendAccessCredentials": { "password_hash_b64": "ha$h" }
}"#;

let err = serde_json::from_str::<SendAccessTokenRequest>(json).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("did not match any variant"),
"unexpected: {msg}"
);
}

#[test]
fn extra_nested_key_is_rejected() {
let json = r#"
{
"sendId": "abc123",
"sendAccessCredentials": {
"passwordHashB64": "ha$h",
"extra": "nope"
}
}"#;
let err = from_str::<SendAccessTokenRequest>(json).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("did not match any variant"),
"unexpected: {msg}"
);
}
}

mod send_access_credentials_tests {
use super::*;

mod send_access_password_credentials_tests {
use serde_json::{from_str, to_string};

use super::*;
#[test]
fn deserializes_camelcase_from_ts() {
let json = r#"{ "passwordHashB64": "ha$h" }"#;
let s: SendPasswordCredentials = from_str(json).unwrap();
assert_eq!(s.password_hash_b64, "ha$h");
}

#[test]
fn serializes_camelcase_to_wire() {
let s = SendPasswordCredentials {
password_hash_b64: "ha$h".into(),
};
let json = to_string(&s).unwrap();
assert_eq!(json, r#"{"passwordHashB64":"ha$h"}"#);
}

#[test]
fn roundtrip_camel_in_to_camel_out() {
let in_json = r#"{ "passwordHashB64": "ha$h" }"#;
let parsed: SendPasswordCredentials = from_str(in_json).unwrap();
let out_json = to_string(&parsed).unwrap();
assert_eq!(out_json, r#"{"passwordHashB64":"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"}"#);
}
}
}
Loading
Loading