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
58impl From<TokenGenerator> for TokenProvider {
59 fn from(generator: TokenGenerator) -> Self {
60 TokenProvider::Generator(generator)
61 }
62}
63
64impl From<String> for TokenProvider {
65 fn from(token: String) -> Self {
66 TokenProvider::Static(token)
67 }
68}
69
70impl From<&str> for TokenProvider {
71 fn from(token: &str) -> Self {
72 TokenProvider::Static(token.to_owned())
73 }
74}
75
76#[derive(Debug)]
85pub struct TokenGenerator {
86 kid: String,
87 encoding_key: EncodingKey,
88 expiry_seconds: u64,
89 permissions: HashSet<Permission>,
90}
91
92#[derive(Serialize, Deserialize)]
93struct JwtRes {
94 #[serde(rename = "os:usecase")]
95 usecase: String,
96
97 #[serde(flatten)]
98 scopes: BTreeMap<String, String>,
99}
100
101#[derive(Serialize, Deserialize)]
102struct JwtClaims {
103 exp: u64,
104 permissions: HashSet<Permission>,
105 res: JwtRes,
106}
107
108impl TokenGenerator {
109 pub fn new(secret_key: SecretKey) -> crate::Result<TokenGenerator> {
111 let encoding_key = EncodingKey::from_ed_pem(secret_key.secret_key.as_bytes())?;
112 Ok(TokenGenerator {
113 kid: secret_key.kid,
114 encoding_key,
115 expiry_seconds: DEFAULT_EXPIRY_SECONDS,
116 permissions: HashSet::from(DEFAULT_PERMISSIONS),
117 })
118 }
119
120 pub fn expiry_seconds(mut self, expiry_seconds: u64) -> Self {
122 self.expiry_seconds = expiry_seconds;
123 self
124 }
125
126 pub fn permissions(mut self, permissions: &[Permission]) -> Self {
128 self.permissions = HashSet::from_iter(permissions.iter().cloned());
129 self
130 }
131
132 pub fn sign(&self, scope: &crate::Scope) -> crate::Result<String> {
141 let scope = match &scope.0 {
142 Ok(inner) => inner,
143 Err(crate::Error::InvalidScope(err)) => {
144 return Err(err.clone().into());
145 }
146 _ => return Err(scope::InvalidScopeError::Unreachable.into()),
150 };
151 self.sign_for_scope(scope)
152 }
153
154 pub(crate) fn sign_for_scope(&self, scope: &ScopeInner) -> crate::Result<String> {
156 let claims = JwtClaims {
157 exp: get_current_timestamp() + self.expiry_seconds,
158 permissions: self.permissions.clone(),
159 res: JwtRes {
160 usecase: scope.usecase().name().into(),
161 scopes: scope
162 .scopes()
163 .iter()
164 .map(|scope| (scope.name().to_string(), scope.value().to_string()))
165 .collect(),
166 },
167 };
168
169 let mut header = Header::new(Algorithm::EdDSA);
170 header.kid = Some(self.kid.clone());
171
172 Ok(encode(&header, &claims, &self.encoding_key)?)
173 }
174}