Skip to content

Commit aac339b

Browse files
feat(rs-client)!: Rename TokenGenerator to SecretKey
Co-Authored-By: Lorenzo Cian <17258265+lcian@users.noreply.github.com>
1 parent 06b1587 commit aac339b

9 files changed

Lines changed: 107 additions & 143 deletions

File tree

‎clients/rust/README.md‎

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -273,34 +273,26 @@ session
273273
If your Objectstore instance enforces authorization, you must configure authentication
274274
via [`ClientBuilder::token`]. It accepts either:
275275

276-
- A **[`TokenGenerator`]** — for internal services that have access to an EdDSA keypair.
277-
The generator signs a fresh JWT for each request, scoped to the specific usecase
276+
- A **[`SecretKey`]** — for internal services that have access to an EdDSA keypair.
277+
The key signs a fresh JWT for each request, scoped to the specific usecase
278278
and scope being accessed.
279279
- A **`String` / `&str`** — a pre-signed JWT, used as-is for every request.
280280
Use this for external services that receive a token from another source.
281281
- An `Option` of any of the above — useful for chained builder calls.
282282

283283
```rust,ignore
284-
use objectstore_client::{Client, SecretKey, TokenGenerator, Usecase};
284+
use objectstore_client::{Client, SecretKey, Usecase};
285285
286286
// Option 1: Internal service with a keypair
287287
let client = Client::builder("http://localhost:8888/")
288-
.token(
289-
TokenGenerator::new(SecretKey {
290-
secret_key: "<private key>".into(),
291-
kid: "my-service".into(),
292-
})?
293-
)
288+
.token(SecretKey::new("my-service", "<private key>")?)
294289
.build()?;
295290
296291
// Option 2: External service with a pre-signed JWT
297-
// Use TokenGenerator::create_token() to obtain a static token from an authority
292+
// Use SecretKey::create_token() to obtain a static token from an authority
298293
let scope = Usecase::new("my_app").for_project(42, 1337);
299-
let generator = TokenGenerator::new(SecretKey {
300-
secret_key: "<private key>".into(),
301-
kid: "my-service".into(),
302-
})?;
303-
let token = generator.create_token(&scope).sign()?;
294+
let secret_key = SecretKey::new("my-service", "<private key>")?;
295+
let token = secret_key.create_token(&scope).sign()?;
304296
305297
// Then pass the token directly to a client builder.
306298
let client = Client::builder("http://localhost:8888/")
@@ -326,7 +318,7 @@ static CLIENT: LazyLock<Client> = LazyLock::new(|| {
326318
// .propagate_traces(true) // default: false
327319
// .timeout(Duration::from_secs(5)) // default: no read timeout (connect: 100ms)
328320
// .configure_reqwest(|builder| { ... }) // customize the reqwest::ClientBuilder
329-
// .token(token_generator) // see Authentication section
321+
// .token(secret_key) // see Authentication section
330322
.build()
331323
.expect("Objectstore client to build successfully")
332324
});
@@ -344,7 +336,7 @@ async fn example() -> Result<()> {
344336
```
345337

346338
See [`ClientBuilder`] for all available options, including authentication via
347-
[`TokenGenerator`].
339+
[`SecretKey`].
348340

349341
See the [API docs](https://getsentry.github.io/objectstore/rust/objectstore_client/) for full reference documentation.
350342

‎clients/rust/src/auth.rs‎

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,32 @@ const DEFAULT_PERMISSIONS: [Permission; 3] = [
1515
Permission::ObjectDelete,
1616
];
1717

18-
/// Key configuration that will be used to sign tokens in Objectstore requests.
19-
pub struct SecretKey {
20-
/// A key ID that Objectstore must use to load the corresponding public key.
21-
pub kid: String,
22-
23-
/// An EdDSA private key.
24-
pub secret_key: String,
25-
}
26-
27-
impl 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-
3618
/// Authentication provider for Objectstore requests.
3719
///
38-
/// Can be either a [`TokenGenerator`] that signs a fresh JWT per request,
20+
/// Can be either a [`SecretKey`] that signs a fresh JWT per request,
3921
/// or a static pre-signed JWT string.
4022
pub enum TokenProvider {
4123
/// A pre-signed JWT token string, used as-is for every request.
4224
Static(String),
43-
/// A generator that signs a fresh JWT for each request using an EdDSA keypair.
44-
Generator(TokenGenerator),
25+
/// A secret key that signs a fresh JWT for each request using an EdDSA keypair.
26+
SecretKey(SecretKey),
4527
}
4628

4729
impl std::fmt::Debug for TokenProvider {
4830
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4931
match self {
5032
TokenProvider::Static(_) => f.write_str("TokenProvider::Static([redacted])"),
51-
TokenProvider::Generator(g) => {
52-
f.debug_tuple("TokenProvider::Generator").field(g).finish()
53-
}
33+
TokenProvider::SecretKey(key) => f
34+
.debug_tuple("TokenProvider::SecretKey")
35+
.field(key)
36+
.finish(),
5437
}
5538
}
5639
}
5740

5841
/// Conversion into an optional [`TokenProvider`] for [`ClientBuilder::token`].
5942
///
60-
/// This is implemented for [`TokenGenerator`], `String`, and `&str`, each of which yields a
43+
/// This is implemented for [`SecretKey`], `String`, and `&str`, each of which yields a
6144
/// configured provider. It is also implemented for any `Option<T>` where `T: IntoTokenProvider`,
6245
/// so a `None` resolves to no authentication and a `Some(value)` to the inner provider. This lets
6346
/// callers pass optional auth configuration to [`ClientBuilder::token`] without an explicit
@@ -78,9 +61,9 @@ where
7861
}
7962
}
8063

81-
impl IntoTokenProvider for TokenGenerator {
64+
impl IntoTokenProvider for SecretKey {
8265
fn into_token_provider(self) -> Option<TokenProvider> {
83-
Some(TokenProvider::Generator(self))
66+
Some(TokenProvider::SecretKey(self))
8467
}
8568
}
8669

@@ -96,17 +79,17 @@ impl IntoTokenProvider for &str {
9679
}
9780
}
9881

99-
/// A utility to generate auth tokens to be used in Objectstore requests.
82+
/// An EdDSA keypair used to sign auth tokens for Objectstore requests.
10083
///
101-
/// Tokens are signed with an EdDSA private key and have certain permissions and expiry timeouts
84+
/// Tokens are signed with the private key and have certain permissions and expiry timeouts
10285
/// applied.
10386
///
104-
/// Use this for internal services that have access to an EdDSA keypair. A `TokenGenerator`
87+
/// Use this for internal services that have access to an EdDSA keypair. A `SecretKey`
10588
/// implements [`IntoTokenProvider`], so it can be passed directly to
10689
/// [`ClientBuilder::token`](crate::ClientBuilder::token), where it becomes a
107-
/// [`TokenProvider::Generator`].
90+
/// [`TokenProvider::SecretKey`].
10891
#[derive(Debug)]
109-
pub struct TokenGenerator {
92+
pub struct SecretKey {
11093
kid: String,
11194
encoding_key: EncodingKey,
11295
expiry_seconds: u64,
@@ -129,25 +112,31 @@ struct JwtClaims {
129112
res: JwtRes,
130113
}
131114

132-
impl TokenGenerator {
133-
/// Create a new [`TokenGenerator`] for a given key configuration.
134-
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,
115+
impl SecretKey {
116+
/// Creates a new [`SecretKey`] from a key ID and a PEM-encoded EdDSA private key.
117+
///
118+
/// `kid` is the key ID that Objectstore uses to load the corresponding public key.
119+
///
120+
/// # Errors
121+
///
122+
/// Returns an error if `secret_key` is not a valid PEM-encoded EdDSA private key.
123+
pub fn new(kid: impl Into<String>, secret_key: &str) -> crate::Result<SecretKey> {
124+
let encoding_key = EncodingKey::from_ed_pem(secret_key.as_bytes())?;
125+
Ok(SecretKey {
126+
kid: kid.into(),
138127
encoding_key,
139128
expiry_seconds: DEFAULT_EXPIRY_SECONDS,
140129
permissions: HashSet::from(DEFAULT_PERMISSIONS),
141130
})
142131
}
143132

144-
/// Set the expiry duration for tokens signed by this generator.
133+
/// Set the expiry duration for tokens signed by this key.
145134
pub fn expiry_seconds(mut self, expiry_seconds: u64) -> Self {
146135
self.expiry_seconds = expiry_seconds;
147136
self
148137
}
149138

150-
/// Set the permissions that will be granted to tokens signed by this generator.
139+
/// Set the permissions that will be granted to tokens signed by this key.
151140
pub fn permissions(mut self, permissions: &[Permission]) -> Self {
152141
self.permissions = HashSet::from_iter(permissions.iter().copied());
153142
self
@@ -158,11 +147,11 @@ impl TokenGenerator {
158147
/// Use this to produce a static token that can be handed to an external service
159148
/// which then passes it to [`ClientBuilder::token`](crate::ClientBuilder::token).
160149
///
161-
/// By default, the token is signed with the generator's default permissions and expiry.
150+
/// By default, the token is signed with the key's default permissions and expiry.
162151
/// Use the builder methods on the returned [`TokenRequest`] to customize them.
163152
pub fn create_token<'a>(&'a self, scope: &'a Scope) -> TokenRequest<'a> {
164153
TokenRequest {
165-
generator: self,
154+
key: self,
166155
scope: scope.0.as_ref(),
167156
permissions: None,
168157
expiry_seconds: None,
@@ -171,15 +160,15 @@ impl TokenGenerator {
171160

172161
pub(crate) fn request_inner<'a>(&'a self, scope: &'a ScopeInner) -> TokenRequest<'a> {
173162
TokenRequest {
174-
generator: self,
163+
key: self,
175164
scope: Ok(scope),
176165
permissions: None,
177166
expiry_seconds: None,
178167
}
179168
}
180169

181170
/// Resolves the permissions to embed in a token, validating that any explicitly requested
182-
/// permissions are a subset of those granted to this generator.
171+
/// permissions are a subset of those granted to this key.
183172
fn resolve_permissions(
184173
&self,
185174
requested: Option<&[Permission]>,
@@ -199,24 +188,24 @@ impl TokenGenerator {
199188
}
200189
}
201190

202-
/// A request to mint a new token returned from [`TokenGenerator::create_token`].
191+
/// A request to mint a new token returned from [`SecretKey::create_token`].
203192
#[derive(Debug)]
204193
pub struct TokenRequest<'a> {
205-
generator: &'a TokenGenerator,
194+
key: &'a SecretKey,
206195
scope: Result<&'a ScopeInner, &'a crate::Error>,
207196
permissions: Option<Vec<Permission>>,
208197
expiry_seconds: Option<u64>,
209198
}
210199

211200
impl TokenRequest<'_> {
212-
/// Override the permissions for this token, which must be a subset of the generator's
201+
/// Override the permissions for this token, which must be a subset of the key's
213202
/// permissions.
214203
pub fn permissions(mut self, permissions: &[Permission]) -> Self {
215204
self.permissions = Some(permissions.to_vec());
216205
self
217206
}
218207

219-
/// Set the expiry duration for this token, overriding the generator's default.
208+
/// Set the expiry duration for this token, overriding the key's default.
220209
pub fn expiry_seconds(mut self, expiry_seconds: u64) -> Self {
221210
self.expiry_seconds = Some(expiry_seconds);
222211
self
@@ -238,11 +227,8 @@ impl TokenRequest<'_> {
238227
};
239228

240229
let claims = JwtClaims {
241-
exp: get_current_timestamp()
242-
+ self.expiry_seconds.unwrap_or(self.generator.expiry_seconds),
243-
permissions: self
244-
.generator
245-
.resolve_permissions(self.permissions.as_deref())?,
230+
exp: get_current_timestamp() + self.expiry_seconds.unwrap_or(self.key.expiry_seconds),
231+
permissions: self.key.resolve_permissions(self.permissions.as_deref())?,
246232
res: JwtRes {
247233
usecase: scope.usecase().name().into(),
248234
scopes: scope
@@ -254,8 +240,8 @@ impl TokenRequest<'_> {
254240
};
255241

256242
let mut header = Header::new(Algorithm::EdDSA);
257-
header.kid = Some(self.generator.kid.clone());
243+
header.kid = Some(self.key.kid.clone());
258244

259-
Ok(encode(&header, &claims, &self.generator.encoding_key)?)
245+
Ok(encode(&header, &claims, &self.key.encoding_key)?)
260246
}
261247
}

‎clients/rust/src/client.rs‎

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ impl ClientBuilder {
113113
/// Sets the authentication token to use for requests to Objectstore.
114114
///
115115
/// Accepts anything that implements [`IntoTokenProvider`]:
116-
/// - A [`TokenGenerator`](crate::TokenGenerator) — for internal services that have access to
117-
/// an EdDSA keypair. The generator signs a fresh JWT for each request.
116+
/// - A [`SecretKey`](crate::SecretKey) — for internal services that have access to
117+
/// an EdDSA keypair. The key signs a fresh JWT for each request.
118118
/// - A `String` or `&str` — a pre-signed JWT, used as-is for every request.
119119
/// - An `Option` of any of the above — a `None` leaves the client unauthenticated, which is
120120
/// convenient when authentication is configured conditionally.
@@ -340,8 +340,8 @@ pub(crate) struct ClientInner {
340340
/// authentication via [`ClientBuilder::token`]. It accepts anything that converts
341341
/// into a [`TokenProvider`]:
342342
///
343-
/// - **[`TokenGenerator`](crate::TokenGenerator)** — for internal services that have access to
344-
/// an EdDSA keypair. The generator signs a fresh JWT for each request, scoped to the
343+
/// - **[`SecretKey`](crate::SecretKey)** — for internal services that have access to
344+
/// an EdDSA keypair. The key signs a fresh JWT for each request, scoped to the
345345
/// specific usecase and scope being accessed.
346346
/// - **`String` / `&str`** — a pre-signed JWT, used as-is for every request.
347347
/// Use this for external services that receive a token from another source.
@@ -352,38 +352,34 @@ pub(crate) struct ClientInner {
352352
///
353353
/// ```no_run
354354
/// use std::time::Duration;
355-
/// use objectstore_client::{Client, SecretKey, TokenGenerator, Usecase};
355+
/// use objectstore_client::{Client, SecretKey, Usecase};
356356
/// use objectstore_types::auth::Permission;
357357
///
358358
/// # async fn example() -> objectstore_client::Result<()> {
359-
/// let token_generator = TokenGenerator::new(SecretKey {
360-
/// secret_key: "<safely inject secret key>".into(),
361-
/// kid: "my-service".into(),
362-
/// })?
359+
/// let secret_key = SecretKey::new("my-service", "<safely inject secret key>")?
363360
/// .expiry_seconds(30)
364361
/// .permissions(&[Permission::ObjectRead]);
365362
///
366363
/// let client = Client::builder("http://localhost:8888/")
367364
/// .timeout(Duration::from_secs(1))
368365
/// .propagate_traces(true)
369-
/// .token(token_generator)
366+
/// .token(secret_key)
370367
/// .build()?;
371368
/// # Ok(())
372369
/// # }
373370
/// ```
374371
///
375372
/// External service with a pre-signed JWT (obtained via
376-
/// [`TokenGenerator::create_token`](crate::TokenGenerator::create_token)):
373+
/// [`SecretKey::create_token`](crate::SecretKey::create_token)):
377374
///
378375
/// ```no_run
379-
/// use objectstore_client::{Client, SecretKey, TokenGenerator, Usecase};
376+
/// use objectstore_client::{Client, SecretKey, Usecase};
380377
///
381378
/// # fn example() -> objectstore_client::Result<()> {
382379
/// let scope = Usecase::new("my_app").for_project(42, 1337);
383-
/// let token = TokenGenerator::new(SecretKey {
384-
/// secret_key: "<private key>".into(),
385-
/// kid: "my-service".into(),
386-
/// })?.create_token(&scope).sign()?;
380+
/// let token = SecretKey::new("my-service", "<private key>")?
381+
/// .create_token(&scope)
382+
/// .sign()?;
387383
///
388384
/// let client = Client::builder("http://localhost:8888/")
389385
/// .token(token)
@@ -481,7 +477,7 @@ impl Session {
481477

482478
/// Returns a token authorizing access to this session's scope.
483479
///
484-
/// With a [`TokenGenerator`](crate::TokenGenerator), each call signs a fresh token whose expiry
480+
/// With a [`SecretKey`](crate::SecretKey), each call signs a fresh token whose expiry
485481
/// starts now, using the configured permissions; use [`create_token`](Self::create_token) to
486482
/// narrow either.
487483
///
@@ -496,8 +492,8 @@ impl Session {
496492
/// pre-signed token never fails.
497493
pub fn get_token(&self) -> crate::Result<Option<String>> {
498494
match self.client.token {
499-
Some(TokenProvider::Generator(ref generator)) => {
500-
generator.request_inner(&self.scope).sign().map(Some)
495+
Some(TokenProvider::SecretKey(ref key)) => {
496+
key.request_inner(&self.scope).sign().map(Some)
501497
}
502498
Some(TokenProvider::Static(ref token)) => Ok(Some(token.clone())),
503499
None => Ok(None),
@@ -517,14 +513,12 @@ impl Session {
517513
///
518514
/// # Errors
519515
///
520-
/// This requires a [`TokenGenerator`](crate::TokenGenerator) to sign a new token.
516+
/// This requires a [`SecretKey`](crate::SecretKey) to sign a new token.
521517
/// If not available, this will result in an error. To retrieve a token with the current
522518
/// session's default permissions and expiry, use [`get_token`](Self::get_token) instead.
523519
pub fn create_token(&self) -> crate::Result<Option<TokenRequest<'_>>> {
524520
match self.client.token {
525-
Some(TokenProvider::Generator(ref generator)) => {
526-
Ok(Some(generator.request_inner(&self.scope)))
527-
}
521+
Some(TokenProvider::SecretKey(ref key)) => Ok(Some(key.request_inner(&self.scope))),
528522
Some(TokenProvider::Static(_)) => Err(crate::Error::StaticTokenOverride),
529523
None => Ok(None),
530524
}

‎clients/rust/src/error.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ pub enum Error {
2323
#[error(transparent)]
2424
TokenError(#[from] jsonwebtoken::errors::Error),
2525
/// Error when the permissions requested for a token exceed those granted to the
26-
/// [`TokenGenerator`](crate::TokenGenerator).
27-
#[error("requested permissions not granted to this token generator: {escalated:?}")]
26+
/// [`SecretKey`](crate::SecretKey).
27+
#[error("requested permissions not granted to this secret key: {escalated:?}")]
2828
PermissionEscalation {
29-
/// The requested permissions that are not granted to the generator.
29+
/// The requested permissions that are not granted to the key.
3030
escalated: Vec<objectstore_types::auth::Permission>,
3131
},
3232
/// Error when per-token permission or expiry overrides are requested for a static

0 commit comments

Comments
 (0)