waterui_form/secure.rs
1//! Secure form components for handling sensitive data.
2//!
3//! This module provides utilities for handling sensitive form data such as
4//! passwords and other secrets with automatic memory zeroing for security.
5
6use core::{fmt::Debug, str::FromStr};
7
8use alloc::string::{String, ToString};
9use nami::Binding;
10use waterui_core::{AnyView, View, configurable, layout::StretchAxis};
11use zeroize::Zeroize;
12
13/// A wrapper type for securely handling sensitive string data.
14#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Secure(String);
16
17impl Debug for Secure {
18 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
19 f.write_str("Secure(****)")
20 }
21}
22
23impl FromStr for Secure {
24 type Err = core::convert::Infallible;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 Ok(Self(s.to_string()))
28 }
29}
30
31impl Secure {
32 /// Creates a new Secure value from a string.
33 ///
34 /// # Arguments
35 ///
36 /// * `value` - The string value to secure
37 ///
38 /// # Returns
39 ///
40 /// A new Secure instance wrapping the provided string.
41 #[must_use]
42 pub const fn new(value: String) -> Self {
43 Self(value)
44 }
45
46 /// Returns the inner string as a string slice.
47 ///
48 /// # Returns
49 ///
50 /// A reference to the inner string data.
51 #[must_use]
52 pub fn expose(&self) -> &str {
53 &self.0
54 }
55
56 /// Sets the value of the secure string.
57 ///
58 /// # Arguments
59 ///
60 /// * `value` - The new string value
61 pub fn set(&mut self, value: String) {
62 self.0.zeroize();
63 self.0 = value;
64 }
65
66 /// Hashes the secure string using bcrypt.
67 ///
68 /// # Returns
69 ///
70 /// A bcrypt hash of the inner string data.
71 #[allow(clippy::missing_panics_doc)] // bcrypt::hash never panics
72 #[must_use]
73 pub fn hash(&self) -> String {
74 bcrypt::hash(self.expose(), bcrypt::DEFAULT_COST).expect("Failed to hash password")
75 }
76}
77
78// Ensure the inner string is zeroed out when dropped
79impl Drop for Secure {
80 fn drop(&mut self) {
81 self.0.zeroize();
82 }
83}
84
85/// Configuration for a secure field component.
86#[derive(Debug)]
87pub struct SecureFieldConfig {
88 /// The label view displayed for the secure field.
89 pub label: AnyView,
90 /// The binding to the secure value being edited.
91 pub value: Binding<Secure>,
92}
93
94configurable!(
95 /// A secure text entry field for passwords and sensitive data.
96 ///
97 /// SecureField masks input and securely stores values with automatic memory zeroing.
98 ///
99 /// # Layout Behavior
100 ///
101 /// SecureField **expands horizontally** to fill available space, but has a fixed height.
102 /// In an `HStack`, it will take up all remaining width after other views are sized.
103 //
104 // ═══════════════════════════════════════════════════════════════════════════
105 // INTERNAL: Layout Contract for Backend Implementers
106 // ═══════════════════════════════════════════════════════════════════════════
107 //
108
109 // Height: Fixed intrinsic (platform-determined)
110 // Width: Reports minimum usable width, expands during layout phase
111 //
112 // Same layout behavior as TextField.
113 //
114 // ═══════════════════════════════════════════════════════════════════════════
115 //
116 SecureField,
117 SecureFieldConfig,
118 StretchAxis::Horizontal
119);
120
121impl SecureField {
122 /// Creates a new `SecureField` instance.
123 ///
124 /// # Arguments
125 ///
126 /// * `label` - A view representing the label for the secure field.
127 /// * `value` - A binding to the `Secure` value that the field will edit.
128 ///
129 /// # Returns
130 ///
131 /// A new `SecureField` instance configured with the provided label and value binding.
132 #[must_use]
133 pub fn new(label: impl View, value: &Binding<Secure>) -> Self {
134 Self(SecureFieldConfig {
135 label: AnyView::new(label),
136 value: value.clone(),
137 })
138 }
139
140 /// Sets the label for the secure field.
141 ///
142 /// # Arguments
143 ///
144 /// * `label` - A view representing the new label for the secure field.
145 ///
146 /// # Returns
147 ///
148 /// A new `SecureField` instance with the updated label.
149 #[must_use]
150 pub fn label(self, label: impl View) -> Self {
151 let mut config = self.0;
152 config.label = AnyView::new(label);
153 Self(config)
154 }
155}
156
157/// Creates a new `SecureField` instance.
158/// See [`SecureField::new`] for more details.
159#[must_use]
160pub fn secure(label: impl View, value: &Binding<Secure>) -> SecureField {
161 SecureField::new(label, value)
162}