-
Notifications
You must be signed in to change notification settings - Fork 41
Auth/PM-40520 - Registration - Open Org Invite Seal / Unseal Support #1295
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
Merged
JaredSnider-Bitwarden
merged 40 commits into
main
from
auth/pm-40520/registration-open-org-invite-seal-unseal
Jul 29, 2026
Merged
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
c59e915
PM-40520 - Add HighEntropySecret base64 wire helpers
JaredSnider-Bitwarden 447cf66
PM-40520 - Add namespace variants for open-invite registration crossing
JaredSnider-Bitwarden b424f8f
PM-40520 - Add RegistrationClient seal/unseal for open-org-invite croβ¦
JaredSnider-Bitwarden 99e1dc1
PM-40520 - Move open-org-invite WASM tests to auth-owned tests/registβ¦
JaredSnider-Bitwarden 238e3bb
PM-40520 - Route tests/registration/** to team-auth-dev in CODEOWNERS
JaredSnider-Bitwarden e843bb4
PM-40520 - Align new WASM-facing types on camelCase serde
JaredSnider-Bitwarden 8509fbe
PM-40520 - Enforce MIN_SECRET_LENGTH in HighEntropySecret::from_base64
JaredSnider-Bitwarden d539696
PM-40520 - Force CBOR bstr encoding for the two envelope bytes fields
JaredSnider-Bitwarden bd2af69
PM-40520 - Teach generate_versioned_sealable! optional visibility
JaredSnider-Bitwarden 4545a48
PM-40520 - Restructure open-invite into module folder, drop macro change
JaredSnider-Bitwarden 62e9925
PM-40520 - Trim internals from public rustdoc on open-invite types
JaredSnider-Bitwarden f93070a
PM-40520 - Inline seal/unseal bodies into their impl methods
JaredSnider-Bitwarden 4ee3240
PM-40520 - Correct open-org-invite module doc to match app flow
JaredSnider-Bitwarden ba652a2
PM-40520 - HighEntropySecret uses the DataEnvelope trait pattern
JaredSnider-Bitwarden 8314bc9
PM-40520 - Strongly type SealedOpenOrgInvite fields
JaredSnider-Bitwarden 5874d84
PM-40520 - Extract OPEN_ORG_INVITE_SECRET_SIZE_BYTES const
JaredSnider-Bitwarden 3e556bb
PM-40520 - Add public-API integration tests + pinned wire vector
JaredSnider-Bitwarden a8b6c65
PM-40520 - Small doc + TS-type touch-ups from PR review
JaredSnider-Bitwarden 8f2f989
PM-40520 - Scope unseal doc to post-parse failures
JaredSnider-Bitwarden 3ba969f
PM-40520 - Rename wire_v1 to data_v1 and clarify layer docs
JaredSnider-Bitwarden 9d2a514
PM-40520 - Rename SealedEnvelopePair to SealedOpenOrgInviteData
JaredSnider-Bitwarden 60054b9
PM-40520 - Fill test gaps for HighEntropySecret serde + key-envelope β¦
JaredSnider-Bitwarden 791d079
Merge branch 'main' into auth/pm-40520/registration-open-org-invite-sβ¦
JaredSnider-Bitwarden 2fd721f
PM-40520 - Rename OpenOrgInviteSealRequest to OpenOrgInvite
JaredSnider-Bitwarden 29f5a0f
PM-40520 - Fully-qualify cross-crate intra-doc links
JaredSnider-Bitwarden 86e80b3
PM-40520 - Adjust SecretProtectedKeyEnvelopeNamespace per feedback
JaredSnider-Bitwarden 3ccff9e
PM-40520 - remove overdocumented comments
JaredSnider-Bitwarden ba7fbd5
PM-40520 - Rename invite_key to invite_secret
JaredSnider-Bitwarden 21a1c26
PM-40520 - Adjust OPEN_ORG_INVITE_SECRET_SIZE_BYTES comment to be shoβ¦
JaredSnider-Bitwarden 44f21d7
PM-40520 - fix OpenOrgInvite docs
JaredSnider-Bitwarden 9ed4201
PM-40520 - Remove low level comment paragraph per PR feedback
JaredSnider-Bitwarden ff72d85
PM-40520 - Trim wordy comments in seal, unseal, and integration tests
JaredSnider-Bitwarden af343e3
Merge remote-tracking branch 'origin/main' into auth/pm-40520/registrβ¦
JaredSnider-Bitwarden e7a936f
PM-40520 - Rename inviteKey to inviteSecret in integration test + fixβ¦
JaredSnider-Bitwarden a40ca8b
PM-40520 - Move seal/unseal logic onto SealedOpenOrgInviteData
JaredSnider-Bitwarden 68a3357
PM-40520 - Trim SealedOpenOrgInviteDataWire doc, move serde_bytes ratβ¦
JaredSnider-Bitwarden 5c5deb7
PM-40520 - Define CBOR and WASM ABI inline in serialization module doc
JaredSnider-Bitwarden b45ac31
PM-40520 - Update stale SealedEnvelopePair reference in error message
JaredSnider-Bitwarden 8518461
Merge remote-tracking branch 'origin/main' into auth/pm-40520/registrβ¦
JaredSnider-Bitwarden abc1810
PM-40520 - Restore .github/CODEOWNERS after merge resolution ran it tβ¦
JaredSnider-Bitwarden File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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
95 changes: 95 additions & 0 deletions
95
crates/bitwarden-auth/src/registration/open_org_invite_crypto/client.rs
This file contains hidden or 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,95 @@ | ||
| //! FFI-facing seal/unseal. The [`RegistrationClient`] methods are thin wrappers over | ||
| //! [`SealedOpenOrgInviteData::seal`] / [`SealedOpenOrgInviteData::unseal`]; | ||
| //! [`SealedOpenOrgInvite`] bundles both halves of the seal output as one WASM return type. | ||
|
|
||
| use bitwarden_crypto::safe::HighEntropySecret; | ||
| use serde::{Deserialize, Serialize}; | ||
| #[cfg(feature = "wasm")] | ||
| use tsify::Tsify; | ||
| #[cfg(feature = "wasm")] | ||
| use wasm_bindgen::prelude::*; | ||
|
|
||
| use super::{OpenOrgInvite, SealedOpenOrgInviteData}; | ||
| use crate::registration::registration_client::{RegistrationClient, RegistrationError}; | ||
|
|
||
| /// Sealed open-organization-invite payload. Produced by | ||
| /// [`RegistrationClient::seal_open_org_invite_data`] and consumed by | ||
| /// [`RegistrationClient::unseal_open_org_invite_data`]. Both fields are required to unseal; | ||
| /// neither half is useful on its own. | ||
| #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
| #[derive(Serialize, Deserialize, Debug, Clone)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct SealedOpenOrgInvite { | ||
| /// URL-safe opaque payload; place on the verification-email link. | ||
| pub sealed_data: SealedOpenOrgInviteData, | ||
| /// Paired secret; keep client-side (e.g. `localStorage`) and never send to the server. | ||
| pub high_entropy_secret: HighEntropySecret, | ||
| } | ||
|
|
||
| #[cfg_attr(feature = "wasm", wasm_bindgen)] | ||
| impl RegistrationClient { | ||
| /// Seals an [`OpenOrgInvite`] into a [`SealedOpenOrgInvite`]. The returned | ||
| /// `sealed_data` is safe to place on the verification-email link; the returned | ||
| /// `high_entropy_secret` must stay client-side. | ||
| pub fn seal_open_org_invite_data( | ||
| &self, | ||
| input: OpenOrgInvite, | ||
| ) -> Result<SealedOpenOrgInvite, RegistrationError> { | ||
| let (sealed_data, high_entropy_secret) = SealedOpenOrgInviteData::seal(input)?; | ||
| Ok(SealedOpenOrgInvite { | ||
| sealed_data, | ||
| high_entropy_secret, | ||
| }) | ||
| } | ||
|
|
||
| /// Unseals a [`SealedOpenOrgInvite`] back into an [`OpenOrgInvite`]. Returns | ||
| /// [`RegistrationError::Crypto`] if the paired secret does not match the sealed payload or | ||
| /// the payload has been tampered with. | ||
| pub fn unseal_open_org_invite_data( | ||
| &self, | ||
| sealed: SealedOpenOrgInvite, | ||
| ) -> Result<OpenOrgInvite, RegistrationError> { | ||
| sealed.sealed_data.unseal(&sealed.high_entropy_secret) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use bitwarden_core::Client; | ||
|
|
||
| use super::*; | ||
|
|
||
| fn sample_input() -> OpenOrgInvite { | ||
| OpenOrgInvite { | ||
| organization_id: "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".to_string(), | ||
| invite_link_code: "abcd1234efgh5678".to_string(), | ||
| invite_secret: "raw-invite-secret-material-base64url".to_string(), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn sealed_open_org_invite_json_wire_shape_is_stable() { | ||
| // Locks the JSON wire: two-key camelCase object, both values as strings. | ||
| let client = Client::new(None); | ||
| let registration_client = RegistrationClient::new(client); | ||
| let sealed = registration_client | ||
| .seal_open_org_invite_data(sample_input()) | ||
| .expect("seal should succeed"); | ||
|
|
||
| let json = serde_json::to_value(&sealed).expect("serialize"); | ||
| let obj = json.as_object().expect("must be a JSON object"); | ||
| assert_eq!(obj.len(), 2, "no extra or missing fields"); | ||
| assert!( | ||
| obj.get("sealedData") | ||
| .expect("sealedData key must be present") | ||
| .is_string(), | ||
| "sealedData must serialize as a JSON string" | ||
| ); | ||
| assert!( | ||
| obj.get("highEntropySecret") | ||
| .expect("highEntropySecret key must be present") | ||
| .is_string(), | ||
| "highEntropySecret must serialize as a JSON string" | ||
| ); | ||
| } | ||
| } |
21 changes: 21 additions & 0 deletions
21
crates/bitwarden-auth/src/registration/open_org_invite_crypto/data_v1.rs
This file contains hidden or 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,21 @@ | ||
| //! Version 1 of the open-org-invite plaintext payload β the innermost thing sealed by the | ||
| //! [`bitwarden_crypto::safe::DataEnvelope`]. Not to be confused with the sealed opaque blob | ||
| //! ([`super::SealedOpenOrgInviteData`]) or the outbound JSON ([`super::SealedOpenOrgInvite`]); | ||
| //! this file describes only the cleartext shape that gets CBOR-encoded and encrypted. | ||
| //! | ||
| //! Once this shape ships it cannot be broken: any field change means adding a new `V2` struct | ||
| //! and registering both variants on the versioned enum in [`super`], so old sealed payloads | ||
| //! still unseal. | ||
|
|
||
| use bitwarden_crypto::safe::SealableData; | ||
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| #[derive(Serialize, Deserialize, Debug, PartialEq)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub(super) struct RegistrationOpenOrgInviteDataV1 { | ||
| pub(super) organization_id: String, | ||
| pub(super) invite_link_code: String, | ||
| pub(super) invite_secret: String, | ||
| } | ||
|
|
||
| impl SealableData for RegistrationOpenOrgInviteDataV1 {} |
28 changes: 28 additions & 0 deletions
28
crates/bitwarden-auth/src/registration/open_org_invite_crypto/mod.rs
This file contains hidden or 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,28 @@ | ||
| //! Open-organization-invite registration crossing. | ||
| //! | ||
| //! The app seals an invite context on registration-start submit and unseals it on the accept | ||
| //! open-org-invite component after a successful registration-finish. This module owns the | ||
| //! versioned plaintext payload (`data_v1`), the domain types and crypto operations | ||
| //! (`open_org_invite`), their wire encoding (`serialization`), and the FFI-facing client | ||
| //! methods (`client`). | ||
|
|
||
| mod client; | ||
| mod data_v1; | ||
| mod open_org_invite; | ||
| mod serialization; | ||
|
|
||
| use bitwarden_crypto::{ | ||
| generate_versioned_sealable, | ||
| safe::{DataEnvelopeNamespace, SealableData, SealableVersionedData}, | ||
| }; | ||
| pub use client::SealedOpenOrgInvite; | ||
| use data_v1::RegistrationOpenOrgInviteDataV1; | ||
| pub use open_org_invite::{OpenOrgInvite, SealedOpenOrgInviteData}; | ||
| use serde::{Deserialize, Serialize}; | ||
| pub use serialization::SealedOpenOrgInviteDataError; | ||
|
|
||
| generate_versioned_sealable!( | ||
| RegistrationOpenOrgInviteData, | ||
| DataEnvelopeNamespace::RegistrationOpenOrgInviteData, | ||
| [RegistrationOpenOrgInviteDataV1 => "1"] | ||
| ); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.