ic_auth/
session_request.rs1use crate::canonical::{CanonicalAuthError, validate_scope_label};
7use sha2::{Digest, Sha256};
8use thiserror::Error;
9
10#[derive(Debug, Error, Eq, PartialEq)]
12pub enum SessionRequestError {
13 #[error("session request has invalid scope bounds, duplicate scopes or zero TTL")]
14 InvalidRequest,
15 #[error(transparent)]
16 Canonical(#[from] CanonicalAuthError),
17}
18
19#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct SessionRequest {
23 scopes: Vec<String>,
24 ttl_secs: Option<u64>,
25}
26impl SessionRequest {
27 pub fn new(
28 mut scopes: Vec<String>,
29 ttl_secs: Option<u64>,
30 ) -> Result<Self, SessionRequestError> {
31 check_scopes(&scopes)?;
32 scopes.sort_unstable();
33 if scopes.windows(2).any(|pair| pair[0] == pair[1]) || ttl_secs == Some(0) {
34 return Err(SessionRequestError::InvalidRequest);
35 }
36 Ok(Self { scopes, ttl_secs })
37 }
38 pub const fn ttl_secs(&self) -> Option<u64> {
40 self.ttl_secs
41 }
42 pub fn scopes(&self) -> &[String] {
43 &self.scopes
44 }
45 pub fn hash(&self) -> [u8; 32] {
47 let mut hash = Sha256::new();
48 hash.update(b"canic-application-session-request-v1");
49 hash.update((self.scopes.len() as u64).to_be_bytes());
50 for scope in &self.scopes {
51 hash.update((scope.len() as u64).to_be_bytes());
52 hash.update(scope.as_bytes());
53 }
54 match self.ttl_secs {
55 None => hash.update([0]),
56 Some(ttl) => {
57 hash.update([1]);
58 hash.update(ttl.to_be_bytes());
59 }
60 }
61 hash.finalize().into()
62 }
63}
64
65pub(crate) fn check_scopes(scopes: &[String]) -> Result<(), SessionRequestError> {
66 if scopes.is_empty() || scopes.len() > 16 {
67 return Err(SessionRequestError::InvalidRequest);
68 }
69 let mut bytes = 0usize;
70 for scope in scopes {
71 validate_scope_label(scope)?;
72 bytes += scope.len(); }
74 if bytes > 1024 {
75 return Err(SessionRequestError::InvalidRequest);
76 }
77 Ok(())
78}