use std::collections::{BTreeMap, HashSet};
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode, get_current_timestamp};
use objectstore_types::scope;
use serde::{Deserialize, Serialize};
use crate::ScopeInner;
pub use objectstore_types::auth::Permission;
const DEFAULT_EXPIRY_SECONDS: u64 = 60;
const DEFAULT_PERMISSIONS: [Permission; 3] = [
Permission::ObjectRead,
Permission::ObjectWrite,
Permission::ObjectDelete,
];
pub struct SecretKey {
pub kid: String,
pub secret_key: String,
}
impl std::fmt::Debug for SecretKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecretKey")
.field("kid", &self.kid)
.field("secret_key", &"[redacted]")
.finish()
}
}
pub enum TokenProvider {
Static(String),
Generator(TokenGenerator),
}
impl std::fmt::Debug for TokenProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenProvider::Static(_) => f.write_str("TokenProvider::Static([redacted])"),
TokenProvider::Generator(g) => {
f.debug_tuple("TokenProvider::Generator").field(g).finish()
}
}
}
}
pub trait IntoTokenProvider {
fn into_token_provider(self) -> Option<TokenProvider>;
}
impl<T> IntoTokenProvider for Option<T>
where
T: IntoTokenProvider,
{
fn into_token_provider(self) -> Option<TokenProvider> {
self.and_then(|t| t.into_token_provider())
}
}
impl IntoTokenProvider for TokenGenerator {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Generator(self))
}
}
impl IntoTokenProvider for String {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Static(self))
}
}
impl IntoTokenProvider for &str {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Static(self.to_owned()))
}
}
#[derive(Debug)]
pub struct TokenGenerator {
kid: String,
encoding_key: EncodingKey,
expiry_seconds: u64,
permissions: HashSet<Permission>,
}
#[derive(Serialize, Deserialize)]
struct JwtRes {
#[serde(rename = "os:usecase")]
usecase: String,
#[serde(flatten)]
scopes: BTreeMap<String, String>,
}
#[derive(Serialize, Deserialize)]
struct JwtClaims {
exp: u64,
permissions: HashSet<Permission>,
res: JwtRes,
}
impl TokenGenerator {
pub fn new(secret_key: SecretKey) -> crate::Result<TokenGenerator> {
let encoding_key = EncodingKey::from_ed_pem(secret_key.secret_key.as_bytes())?;
Ok(TokenGenerator {
kid: secret_key.kid,
encoding_key,
expiry_seconds: DEFAULT_EXPIRY_SECONDS,
permissions: HashSet::from(DEFAULT_PERMISSIONS),
})
}
pub fn expiry_seconds(mut self, expiry_seconds: u64) -> Self {
self.expiry_seconds = expiry_seconds;
self
}
pub fn permissions(mut self, permissions: &[Permission]) -> Self {
self.permissions = HashSet::from_iter(permissions.iter().copied());
self
}
pub fn sign(&self, scope: &crate::Scope) -> crate::Result<String> {
let scope = match &scope.0 {
Ok(inner) => inner,
Err(crate::Error::InvalidScope(err)) => {
return Err(err.clone().into());
}
_ => return Err(scope::InvalidScopeError::Unreachable.into()),
};
self.sign_for_scope(scope)
}
pub(crate) fn sign_for_scope(&self, scope: &ScopeInner) -> crate::Result<String> {
let claims = JwtClaims {
exp: get_current_timestamp() + self.expiry_seconds,
permissions: self.permissions.clone(),
res: JwtRes {
usecase: scope.usecase().name().into(),
scopes: scope
.scopes()
.iter()
.map(|scope| (scope.name().to_string(), scope.value().to_string()))
.collect(),
},
};
let mut header = Header::new(Algorithm::EdDSA);
header.kid = Some(self.kid.clone());
Ok(encode(&header, &claims, &self.encoding_key)?)
}
}