Skip to main content

robius_authentication/
text.rs

1/// The text contents displayed by an authentication prompt.
2pub struct Text<'a, 'b, 'c, 'd, 'e, 'f> {
3    /// The text of the authentication prompt on Android.
4    pub android: AndroidText<'a, 'b, 'c>,
5    /// The description of the authentication prompt on Apple devices.
6    ///
7    /// Appears as "$(binary_name) is trying to $(description)".
8    pub apple: &'d str,
9    /// The description of the authentication prompt on Windows.
10    pub windows: WindowsText<'e, 'f>,
11}
12
13/// The text of the authentication prompt on Android.
14pub struct AndroidText<'a, 'b, 'c> {
15    pub title: &'a str,
16    pub subtitle: Option<&'b str>,
17    pub description: Option<&'c str>,
18}
19
20/// The text of the authentication prompt on Windows,
21/// including a title ("caption") and description ("message").
22pub struct WindowsText<'a, 'b> {
23    #[allow(dead_code)]
24    pub(crate) title: &'a str,
25    #[allow(dead_code)]
26    pub(crate) description: &'b str,
27}
28
29impl<'a, 'b> WindowsText<'a, 'b> {
30    /// Creates a new `WindowsText` instance.
31    ///
32    /// Returns `None` if `title` exceeds 128 bytes in length
33    /// or if `description` exceeds 1024 bytes in length.
34    #[cfg(target_os = "windows")]
35    pub const fn new(title: &'a str, description: &'b str) -> Option<Self> {
36        use windows::Win32::Security::Credentials::{
37            CREDUI_MAX_CAPTION_LENGTH, CREDUI_MAX_MESSAGE_LENGTH,
38        };
39
40        if title.len() <= CREDUI_MAX_CAPTION_LENGTH as usize
41            && description.len() <= CREDUI_MAX_MESSAGE_LENGTH as usize
42        {
43            Some(Self { title, description })
44        } else {
45            None
46        }
47    }
48
49    /// Creates a new `WindowsText` instance.
50    ///
51    /// On Windows, returns `None` if `title` exceeds 128 bytes in length
52    /// or if `description` exceeds 1024 bytes in length. On other targets the
53    /// text is unused, so no validation is performed and this always returns
54    /// `Some`.
55    #[cfg(not(target_os = "windows"))]
56    pub const fn new(title: &'a str, description: &'b str) -> Option<Self> {
57        Some(Self { title, description })
58    }
59
60    /// Creates a new `WindowsText` instance.
61    ///
62    /// The `title` ("caption") will be truncated to at most 128 bytes in length,
63    /// and the `description` ("message") will be truncated to at most 1024 bytes in length.
64    /// Truncation respects UTF-8 character boundaries, so fewer bytes may be kept.
65    #[cfg(target_os = "windows")]
66    pub fn new_truncated(title: &'a str, description: &'b str) -> Self {
67        use windows::Win32::Security::Credentials::{
68            CREDUI_MAX_CAPTION_LENGTH, CREDUI_MAX_MESSAGE_LENGTH,
69        };
70
71        Self {
72            title: truncate_to_char_boundary(title, CREDUI_MAX_CAPTION_LENGTH as usize),
73            description: truncate_to_char_boundary(
74                description,
75                CREDUI_MAX_MESSAGE_LENGTH as usize,
76            ),
77        }
78    }
79
80    /// Creates a new `WindowsText` instance.
81    ///
82    /// On Windows, the `title` ("caption") is truncated to at most 128 bytes and
83    /// the `description` ("message") to at most 1024 bytes, respecting UTF-8
84    /// character boundaries. On other targets the text is unused and is stored
85    /// verbatim without truncation.
86    #[cfg(not(target_os = "windows"))]
87    pub fn new_truncated(title: &'a str, description: &'b str) -> Self {
88        Self { title, description }
89    }
90}
91
92/// Truncates `s` to at most `max_len` bytes without splitting a UTF-8 character.
93#[cfg(target_os = "windows")]
94fn truncate_to_char_boundary(s: &str, max_len: usize) -> &str {
95    if s.len() <= max_len {
96        return s;
97    }
98    let mut end = max_len;
99    while !s.is_char_boundary(end) {
100        end -= 1;
101    }
102    &s[..end]
103}