objectstore_client/
auth.rs1use std::collections::{BTreeMap, HashSet};
2
3use jsonwebtoken::{Algorithm, EncodingKey, Header, encode, get_current_timestamp};
4use objectstore_types::scope;
5use serde::{Deserialize, Serialize};
6
7use crate::ScopeInner;
8
9pub use objectstore_types::auth::Permission;
10
11const DEFAULT_EXPIRY_SECONDS: u64 = 60;
12const DEFAULT_PERMISSIONS: [Permission; 3] = [
13 Permission::ObjectRead,
14 Permission::ObjectWrite,
15 Permission::ObjectDelete,
16];
17
18pub struct SecretKey {
20 pub kid: String,
22
23 pub secret_key: String,
25}
26
27impl std::fmt::Debug for SecretKey {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("SecretKey")
30 .field("kid", &self.kid)
31 .field("secret_key", &"[redacted]")
32 .finish()
33 }
34}
35
36pub enum TokenProvider {
41 Static(String),
43 Generator(TokenGenerator),
45}
46
47impl std::fmt::Debug for TokenProvider {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 TokenProvider::Static(_) => f.write_str("TokenProvider::Static([redacted])"),
51 TokenProvider::Generator(g) => {
52 f.debug_tuple("TokenProvider::Generator").field(g).finish()
53 }
54 }
55 }
56}
57
58pub trait IntoTokenProvider {
68 fn into_token_provider(self) -> Option<TokenProvider>;
70}
71
72impl<T> IntoTokenProvider for Option<T>
73where
74 T: IntoTokenProvider,
75{
76 fn into_token_provider(self) -> Option<TokenProvider> {
77 self.and_then(|t| t.into_token_provider())
78 }
79}
80
81impl IntoTokenProvider for TokenGenerator {
82 fn into_token_provider(self) -> Option<TokenProvider> {
83 Some(TokenProvider::Generator(self))
84 }
85}
86
87impl IntoTokenProvider for String {
88 fn into_token_provider(self) -> Option<TokenProvider> {
89 Some(TokenProvider::Static(self))
90 }
91}
92
93impl IntoTokenProvider for &str {
94 fn into_token_provider(self) -> Option<TokenProvider> {
95 Some(TokenProvider::Static(self.to_owned()))
96 }
97}
98
99#[derive(Debug)]
109pub struct TokenGenerator {
110 kid: String,
111 encoding_key: EncodingKey,
112 expiry_seconds: u64,
113 permissions: HashSet<Permission>,
114}
115
116#[derive(Serialize, Deserialize)]
117struct JwtRes {
118 #[serde(rename = "os:usecase")]
119 usecase: String,
120
121 #[serde(flatten)]
122 scopes: BTreeMap<String, String>,
123}
124
125#[derive(Serialize, Deserialize)]
126struct JwtClaims {
127 exp: u64,
128 permissions: HashSet<Permission>,
129 res: JwtRes,
130}
131
132impl TokenGenerator {
133 pub fn new(secret_key: SecretKey) -> crate::Result<TokenGenerator> {
135 let encoding_key = EncodingKey::from_ed_pem(secret_key.secret_key.as_bytes())?;
136 Ok(TokenGenerator {
137 kid: secret_key.kid,
138 encoding_key,
139 expiry_seconds: DEFAULT_EXPIRY_SECONDS,
140 permissions: HashSet::from(DEFAULT_PERMISSIONS),
141 })
142 }
143
144 pub fn expiry_seconds(mut self, expiry_seconds: u64) -> Self {
146 self.expiry_seconds = expiry_seconds;
147 self
148 }
149
150 pub fn permissions(mut self, permissions: &[Permission]) -> Self {
152 self.permissions = HashSet::from_iter(permissions.iter().copied());
153 self
154 }
155
156 pub fn sign(&self, scope: &crate::Scope) -> crate::Result<String> {
165 let scope = match &scope.0 {
166 Ok(inner) => inner,
167 Err(crate::Error::InvalidScope(err)) => {
168 return Err(err.clone().into());
169 }
170 _ => return Err(scope::InvalidScopeError::Unreachable.into()),
174 };
175 self.sign_for_scope(scope)
176 }
177
178 pub(crate) fn sign_for_scope(&self, scope: &ScopeInner) -> crate::Result<String> {
180 let claims = JwtClaims {
181 exp: get_current_timestamp() + self.expiry_seconds,
182 permissions: self.permissions.clone(),
183 res: JwtRes {
184 usecase: scope.usecase().name().into(),
185 scopes: scope
186 .scopes()
187 .iter()
188 .map(|scope| (scope.name().to_string(), scope.value().to_string()))
189 .collect(),
190 },
191 };
192
193 let mut header = Header::new(Algorithm::EdDSA);
194 header.kid = Some(self.kid.clone());
195
196 Ok(encode(&header, &claims, &self.encoding_key)?)
197 }
198}