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}