Skip to main content

momento_functions_host/
token.rs

1//! Host interface for working with Momento Token apis
2use momento_functions_wit::host::momento::functions::token;
3use momento_functions_wit::host::momento::functions::token::Expires;
4use serde::{Deserialize, Serialize};
5
6/// An error occurred when generating a disposable token
7#[derive(thiserror::Error, Debug)]
8pub enum GenerateDisposableTokenError {
9    /// An error occurred when calling the host generate function
10    #[error(transparent)]
11    TokenError(#[from] token::TokenError),
12}
13
14/// Generate a disposable token with scoped permissions.
15pub fn generate_disposable_token(
16    valid_for_seconds: u32,
17    permissions: Permissions,
18    token_id: Option<String>,
19) -> Result<FunctionHostGenerateDisposableTokenResponse, GenerateDisposableTokenError> {
20    let stringified_permissions = serde_json::to_string(&permissions).map_err(|e| {
21        GenerateDisposableTokenError::TokenError(token::TokenError::InvalidArgument(format!(
22            "Invalid permissions object passed in: {e:?}"
23        )))
24    })?;
25    log::debug!("generated permissions JSON string: {stringified_permissions}");
26    token::generate_disposable_token(
27        Expires { valid_for_seconds },
28        &stringified_permissions,
29        token_id.as_deref(),
30    )
31    .map_err(Into::into)
32    .map(Into::into)
33}
34
35/// The translated response from the .WIT interface to a Rust-native one
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct FunctionHostGenerateDisposableTokenResponse {
38    /// The new, disposable token with scoped permissions
39    pub api_key: String,
40    /// The endpoint to be used against when calling Momento with this token
41    pub endpoint: String,
42    /// How long the token is valid for, in epoch seconds
43    pub valid_until: u64,
44}
45
46impl From<momento_functions_wit::host::momento::functions::token::GenerateDisposableTokenResponse>
47    for FunctionHostGenerateDisposableTokenResponse
48{
49    fn from(
50        response: momento_functions_wit::host::momento::functions::token::GenerateDisposableTokenResponse,
51    ) -> Self {
52        FunctionHostGenerateDisposableTokenResponse {
53            api_key: response.api_key,
54            endpoint: response.endpoint,
55            valid_until: response.valid_until,
56        }
57    }
58}
59
60/// Role defining the level of access to cache operations.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "PascalCase")]
63pub enum CacheRole {
64    /// No cache access permitted.
65    CachePermitNone = 0,
66    /// Full read and write access.
67    CacheReadWrite = 1,
68    /// Read-only access.
69    CacheReadOnly = 2,
70    /// Write-only access to cache data.
71    /// Does not allow conditional write APIs (SetIfNotExists, IncreaseTTL, etc.).
72    CacheWriteOnly = 3,
73}
74
75/// Role defining the level of access to topic operations.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "PascalCase")]
78pub enum TopicRole {
79    /// No topic access permitted.
80    TopicPermitNone = 0,
81    /// Full read and write access.
82    TopicReadWrite = 1,
83    /// Allows subscribing to topics only.
84    TopicReadOnly = 2,
85    /// Allows publishing to topics only.
86    TopicWriteOnly = 3,
87}
88
89/// Role defining the level of access to function operations.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "PascalCase")]
92pub enum FunctionRole {
93    /// No function access permitted.
94    FunctionPermitNone = 0,
95    /// Permission to invoke functions.
96    FunctionInvoke = 1,
97}
98
99/// Top-level permissions structure. Only scoped permissions can be added,
100/// as diposable tokens do not allow the generation of super user tokens.
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub struct Permissions {
104    /// Scoped permissions for your generated token. We wrap this in a top-level
105    /// struct so the generated JSON is compatible with host interpolation.
106    explicit: ExplicitPermissions,
107}
108
109impl Permissions {
110    /// Create new explicit permissions with no access by default.
111    ///
112    /// This creates an empty permission set. Use `with_cache`, `with_topic`,
113    /// and `with_function` to add specific permissions.
114    ///
115    /// # Example
116    ///
117    /// ```rust,no_run
118    /// use momento_functions_host::token::*;
119    /// let perms = Permissions::new()
120    ///     .with_cache(CachePermissions::read_write())
121    ///     .with_topic(TopicPermissions::read_only());
122    /// ```
123    pub fn new() -> Self {
124        Self {
125            explicit: ExplicitPermissions {
126                permissions: Vec::new(),
127            },
128        }
129    }
130
131    /// Add a cache permission.
132    pub fn with_cache(mut self, permission: CachePermissions) -> Self {
133        self.explicit
134            .permissions
135            .push(PermissionsType::CachePermissions(permission));
136        self
137    }
138
139    /// Add a topic permission.
140    pub fn with_topic(mut self, permission: TopicPermissions) -> Self {
141        self.explicit
142            .permissions
143            .push(PermissionsType::TopicPermissions(permission));
144        self
145    }
146
147    /// Add a function permission.
148    pub fn with_function(mut self, permission: FunctionPermissions) -> Self {
149        self.explicit
150            .permissions
151            .push(PermissionsType::FunctionPermissions(permission));
152        self
153    }
154}
155
156impl Default for Permissions {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162/// Wrapper struct for explicit permissions, exists mainly for JSON interpolation within the host.
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct ExplicitPermissions {
165    /// List of individual permission types.
166    pub permissions: Vec<PermissionsType>,
167}
168
169/// Captures the kinds of permissions you can scope your tokens to.
170///
171/// A single permission can be for caches, topics, or functions.
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173#[serde(rename_all = "snake_case")]
174pub enum PermissionsType {
175    /// Cache-related permissions.
176    CachePermissions(CachePermissions),
177    /// Topic-related permissions.
178    TopicPermissions(TopicPermissions),
179    /// Function-related permissions.
180    FunctionPermissions(FunctionPermissions),
181}
182
183/// Selector for targeting specific caches.
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub enum CacheSelector {
187    /// Select a cache by its exact name.
188    CacheName(String),
189}
190
191/// Selector for targeting specific cache items.
192///
193/// Used to limit permissions to particular keys or key prefixes within a cache.
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(rename_all = "snake_case")]
196pub enum CacheItemSelector {
197    /// Select a specific cache key.
198    Key(Vec<u8>),
199    /// Select all keys with a specific prefix.
200    KeyPrefix(Vec<u8>),
201}
202
203/// Permissions for a cache(s).
204///
205/// Defines the role (read, write, or both), which caches are accessible,
206/// and which items within those caches can be accessed.
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208pub struct CachePermissions {
209    /// The level of access granted (read, write, or both).
210    role: CacheRole,
211    /// Has permissions for all caches
212    #[serde(skip_serializing_if = "Option::is_none")]
213    all_caches: Option<()>,
214    /// Which cache(s) this permission applies to.
215    #[serde(skip_serializing_if = "Option::is_none")]
216    cache_selector: Option<CacheSelector>,
217    /// Has permissions for all items
218    #[serde(skip_serializing_if = "Option::is_none")]
219    all_items: Option<()>,
220    /// Which items within the cache(s) this permission applies to.
221    #[serde(skip_serializing_if = "Option::is_none")]
222    item_selector: Option<CacheItemSelector>,
223}
224
225impl CachePermissions {
226    /// Create cache permissions with no access by default.
227    ///
228    /// Defaults to no access on all caches and all items. Use `with_role`,
229    /// `with_cache`, and `with_items` to configure access.
230    ///
231    /// # Example
232    ///
233    /// ```rust,no_run
234    /// use momento_functions_host::token::*;
235    /// let perms = CachePermissions::new()
236    ///     .with_role(CacheRole::CacheReadWrite)
237    ///     .with_cache("my-cache")
238    ///     .with_all_items();
239    /// ```
240    pub fn new() -> Self {
241        Self {
242            role: CacheRole::CachePermitNone,
243            all_caches: None,
244            cache_selector: None,
245            all_items: None,
246            item_selector: None,
247        }
248    }
249
250    /// Create read-write cache permissions.
251    ///
252    /// Defaults to all caches and all items.
253    ///
254    /// # Example
255    ///
256    /// ```rust,no_run
257    /// use momento_functions_host::token::*;
258    /// let perms = CachePermissions::read_write()
259    ///     .with_cache("my-cache");
260    /// ```
261    pub fn read_write() -> Self {
262        Self {
263            role: CacheRole::CacheReadWrite,
264            all_caches: Some(()),
265            cache_selector: None,
266            all_items: Some(()),
267            item_selector: None,
268        }
269    }
270
271    /// Create read-only cache permissions.
272    ///
273    /// Defaults to all caches and all items.
274    ///
275    /// # Example
276    ///
277    /// ```rust,no_run
278    /// use momento_functions_host::token::*;
279    /// let perms = CachePermissions::read_only()
280    ///     .with_cache("my-cache");
281    /// ```
282    pub fn read_only() -> Self {
283        Self {
284            role: CacheRole::CacheReadOnly,
285            all_caches: Some(()),
286            cache_selector: None,
287            all_items: Some(()),
288            item_selector: None,
289        }
290    }
291
292    /// Create write-only cache permissions.
293    ///
294    /// Defaults to all caches and all items.
295    ///
296    /// # Example
297    ///
298    /// ```rust,no_run
299    /// use momento_functions_host::token::*;
300    /// let perms = CachePermissions::write_only()
301    ///     .with_cache("my-cache");
302    /// ```
303    pub fn write_only() -> Self {
304        Self {
305            role: CacheRole::CacheWriteOnly,
306            all_caches: Some(()),
307            cache_selector: None,
308            all_items: Some(()),
309            item_selector: None,
310        }
311    }
312
313    /// Set the access role, such as `CacheReadWrite` or `CacheReadOnly`.
314    pub fn with_role(mut self, role: CacheRole) -> Self {
315        self.role = role;
316        self
317    }
318
319    /// Grant access to all caches.
320    pub fn with_all_caches(mut self) -> Self {
321        self.all_caches = Some(());
322        self
323    }
324
325    /// Grant access to a specific cache.
326    pub fn with_cache(mut self, cache_name: impl Into<String>) -> Self {
327        self.cache_selector = Some(CacheSelector::CacheName(cache_name.into()));
328        self
329    }
330
331    /// Grant access to all items in the cache(s).
332    pub fn with_all_items(mut self) -> Self {
333        self.all_items = Some(());
334        self
335    }
336
337    /// Grant access to a specific key within the cache.
338    pub fn with_key(mut self, key: impl Into<Vec<u8>>) -> Self {
339        self.item_selector = Some(CacheItemSelector::Key(key.into()));
340        self
341    }
342
343    /// Grant access to all keys with a specific prefix.
344    pub fn with_key_prefix(mut self, prefix: impl Into<Vec<u8>>) -> Self {
345        self.item_selector = Some(CacheItemSelector::KeyPrefix(prefix.into()));
346        self
347    }
348}
349
350impl Default for CachePermissions {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356/// Selector for targeting specific topics.
357///
358/// Used to limit permissions to particular topics by name or name prefix.
359#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "snake_case")]
361pub enum TopicSelector {
362    /// Select a topic by its exact name.
363    TopicName(String),
364    /// Select all topics with a specific name prefix.
365    TopicNamePrefix(String),
366}
367
368/// Permissions for a topic(s).
369///
370/// Defines the role (publish, subscribe, or both), which caches contain the topics,
371/// and which specific topics are accessible.
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373pub struct TopicPermissions {
374    /// The level of access granted (publish, subscribe, or both).
375    role: TopicRole,
376    /// Has permissions for all caches
377    #[serde(skip_serializing_if = "Option::is_none")]
378    all_caches: Option<()>,
379    /// Which cache(s) this permission applies to.
380    #[serde(skip_serializing_if = "Option::is_none")]
381    cache_selector: Option<CacheSelector>,
382    /// Has permissions for all topics
383    #[serde(skip_serializing_if = "Option::is_none")]
384    all_topics: Option<()>,
385    #[serde(skip_serializing_if = "Option::is_none")]
386    /// Which topic(s) this permission applies to.
387    topic_selector: Option<TopicSelector>,
388}
389
390impl TopicPermissions {
391    /// Create topic permissions with no access by default.
392    ///
393    /// Defaults to no access for all caches and all topics. Use `with_role`,
394    /// `with_cache`, and topic selectors to configure access.
395    ///
396    /// # Example
397    ///
398    /// ```rust,no_run
399    /// use momento_functions_host::token::*;
400    /// let perms = TopicPermissions::new()
401    ///     .with_role(TopicRole::TopicReadWrite)
402    ///     .with_cache("my-cache")
403    ///     .with_topic("notifications");
404    /// ```
405    pub fn new() -> Self {
406        Self {
407            role: TopicRole::TopicPermitNone,
408            all_caches: None,
409            cache_selector: None,
410            all_topics: None,
411            topic_selector: None,
412        }
413    }
414
415    /// Create read-write topic permissions.
416    ///
417    /// Defaults to all caches and all topics.
418    ///
419    /// # Example
420    ///
421    /// ```rust,no_run
422    /// use momento_functions_host::token::*;
423    /// let perms = TopicPermissions::read_write()
424    ///     .with_topic("notifications");
425    /// ```
426    pub fn read_write() -> Self {
427        Self {
428            role: TopicRole::TopicReadWrite,
429            all_caches: Some(()),
430            cache_selector: None,
431            all_topics: Some(()),
432            topic_selector: None,
433        }
434    }
435
436    /// Create read-only topic permissions.
437    ///
438    /// Defaults to all caches and all topics.
439    ///
440    /// # Example
441    ///
442    /// ```rust,no_run
443    /// use momento_functions_host::token::*;
444    /// let perms = TopicPermissions::read_only()
445    ///     .with_topic("notifications");
446    /// ```
447    pub fn read_only() -> Self {
448        Self {
449            role: TopicRole::TopicReadOnly,
450            all_caches: Some(()),
451            cache_selector: None,
452            all_topics: Some(()),
453            topic_selector: None,
454        }
455    }
456
457    /// Create write-only topic permissions.
458    ///
459    /// Defaults to all caches and all topics.
460    ///
461    /// # Example
462    ///
463    /// ```rust,no_run
464    /// use momento_functions_host::token::*;
465    /// let perms = TopicPermissions::write_only()
466    ///     .with_topic("notifications");
467    /// ```
468    pub fn write_only() -> Self {
469        Self {
470            role: TopicRole::TopicWriteOnly,
471            all_caches: None,
472            cache_selector: None,
473            all_topics: None,
474            topic_selector: None,
475        }
476    }
477
478    /// Set the access role, such as `TopicReadWrite`.
479    pub fn with_role(mut self, role: TopicRole) -> Self {
480        self.role = role;
481        self
482    }
483
484    /// Grant access to topics in all caches.
485    pub fn with_all_caches(mut self) -> Self {
486        self.all_caches = Some(());
487        self
488    }
489
490    /// Grant access to topics in a specific cache by cache name.
491    pub fn with_cache(mut self, cache_name: impl Into<String>) -> Self {
492        self.cache_selector = Some(CacheSelector::CacheName(cache_name.into()));
493        self
494    }
495
496    /// Grant access to all topics.
497    pub fn with_all_topics(mut self) -> Self {
498        self.all_topics = Some(());
499        self
500    }
501
502    /// Grant access to a specific topic by topic name.
503    pub fn with_topic(mut self, topic_name: impl Into<String>) -> Self {
504        self.topic_selector = Some(TopicSelector::TopicName(topic_name.into()));
505        self
506    }
507
508    /// Grant access to all topics with a specific topic name prefix.
509    pub fn with_topic_prefix(mut self, prefix: impl Into<String>) -> Self {
510        self.topic_selector = Some(TopicSelector::TopicNamePrefix(prefix.into()));
511        self
512    }
513}
514
515impl Default for TopicPermissions {
516    fn default() -> Self {
517        Self::new()
518    }
519}
520
521/// Selector for targeting specific functions.
522///
523/// Used to limit permissions to particular functions by name or name prefix.
524#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
525#[serde(rename_all = "snake_case")]
526pub enum FunctionSelector {
527    /// Select a function by its exact name.
528    FunctionName(String),
529    /// Select all functions with a specific name prefix.
530    FunctionNamePrefix(String),
531}
532
533/// Permissions for a function(s).
534///
535/// Defines the role, the caches the function(s) belong to, and the function(s) that
536/// are accessible.
537#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
538pub struct FunctionPermissions {
539    /// The level of access to your function(s).
540    pub role: FunctionRole,
541    /// Grants permissions for functions within all caches
542    #[serde(skip_serializing_if = "Option::is_none")]
543    all_caches: Option<()>,
544    /// Which cache(s) this permission applies to.
545    #[serde(skip_serializing_if = "Option::is_none")]
546    cache_selector: Option<CacheSelector>,
547    /// Has permissions for all functions
548    #[serde(skip_serializing_if = "Option::is_none")]
549    all_functions: Option<()>,
550    /// Which function(s) this permission applies to.
551    #[serde(skip_serializing_if = "Option::is_none")]
552    function_selector: Option<FunctionSelector>,
553}
554
555impl FunctionPermissions {
556    /// Create function permissions with no access by default.
557    ///
558    /// Defaults to no access on all caches and all functions. Use `with_role`,
559    /// `with_cache`, and function selectors to configure access.
560    ///
561    /// # Example
562    ///
563    /// ```rust,no_run
564    /// use momento_functions_host::token::*;
565    /// let perms = FunctionPermissions::new()
566    ///     .with_role(FunctionRole::FunctionInvoke)
567    ///     .with_cache("my-cache")
568    ///     .with_function("my-function");
569    /// ```
570    pub fn new() -> Self {
571        Self {
572            role: FunctionRole::FunctionPermitNone,
573            all_caches: None,
574            cache_selector: None,
575            all_functions: None,
576            function_selector: None,
577        }
578    }
579
580    /// Create invoke function permissions.
581    ///
582    /// Defaults to all caches and all functions.
583    ///
584    /// # Example
585    ///
586    /// ```rust,no_run
587    /// use momento_functions_host::token::*;
588    /// let perms = FunctionPermissions::invoke()
589    ///     .with_function("my-function");
590    /// ```
591    pub fn invoke() -> Self {
592        Self {
593            role: FunctionRole::FunctionInvoke,
594            all_caches: Some(()),
595            cache_selector: None,
596            all_functions: Some(()),
597            function_selector: None,
598        }
599    }
600
601    /// Set the access role.
602    pub fn with_role(mut self, role: FunctionRole) -> Self {
603        self.role = role;
604        self
605    }
606
607    /// Grant access to functions in all caches.
608    pub fn with_all_caches(mut self) -> Self {
609        self.all_caches = Some(());
610        self
611    }
612
613    /// Grant access to functions in a specific cache by name.
614    pub fn with_cache(mut self, cache_name: impl Into<String>) -> Self {
615        self.cache_selector = Some(CacheSelector::CacheName(cache_name.into()));
616        self
617    }
618
619    /// Grant access to all functions.
620    pub fn with_all_functions(mut self) -> Self {
621        self.all_functions = Some(());
622        self
623    }
624
625    /// Grant access to a specific function by name.
626    pub fn with_function(mut self, function_name: impl Into<String>) -> Self {
627        self.function_selector = Some(FunctionSelector::FunctionName(function_name.into()));
628        self
629    }
630
631    /// Grant access to all functions with a specific name prefix.
632    pub fn with_function_prefix(mut self, prefix: impl Into<String>) -> Self {
633        self.function_selector = Some(FunctionSelector::FunctionNamePrefix(prefix.into()));
634        self
635    }
636}
637
638impl Default for FunctionPermissions {
639    fn default() -> Self {
640        Self::new()
641    }
642}