1717
1818use std:: collections:: HashMap ;
1919use std:: fmt:: { Debug , Formatter } ;
20+ use std:: sync:: Arc ;
2021
2122use http:: StatusCode ;
2223use iceberg:: { Error , ErrorKind , Result } ;
@@ -28,6 +29,17 @@ use tokio::sync::Mutex;
2829use crate :: RestCatalogConfig ;
2930use crate :: types:: { ErrorResponse , TokenResponse } ;
3031
32+ /// Trait for custom token authentication.
33+ ///
34+ /// Implement this trait to provide custom token generation/refresh logic
35+ /// instead of using OAuth credentials.
36+ #[ async_trait:: async_trait]
37+ pub trait CustomAuthenticator : Send + Sync + Debug {
38+ /// Get or refresh the authentication token.
39+ /// Called when the client needs a token for authentication.
40+ async fn get_token ( & self ) -> Result < String > ;
41+ }
42+
3143pub ( crate ) struct HttpClient {
3244 client : Client ,
3345
@@ -39,6 +51,8 @@ pub(crate) struct HttpClient {
3951 token_endpoint : String ,
4052 /// The credential to be used for authentication.
4153 credential : Option < ( Option < String > , String ) > ,
54+ /// Custom token authenticator (takes precedence over credential/token)
55+ authenticator : Option < Arc < dyn CustomAuthenticator > > ,
4256 /// Extra headers to be added to each request.
4357 extra_headers : HeaderMap ,
4458 /// Extra oauth parameters to be added to each authentication request.
@@ -63,6 +77,7 @@ impl HttpClient {
6377 token : Mutex :: new ( cfg. token ( ) ) ,
6478 token_endpoint : cfg. get_token_endpoint ( ) ,
6579 credential : cfg. credential ( ) ,
80+ authenticator : None ,
6681 extra_headers,
6782 extra_oauth_params : cfg. extra_oauth_params ( ) ,
6883 } )
@@ -86,6 +101,7 @@ impl HttpClient {
86101 self . token_endpoint
87102 } ,
88103 credential : cfg. credential ( ) . or ( self . credential ) ,
104+ authenticator : self . authenticator ,
89105 extra_headers,
90106 extra_oauth_params : if !cfg. extra_oauth_params ( ) . is_empty ( ) {
91107 cfg. extra_oauth_params ( )
@@ -174,6 +190,27 @@ impl HttpClient {
174190 Ok ( auth_res. access_token )
175191 }
176192
193+ /// Set a custom token authenticator.
194+ ///
195+ /// When set, the authenticator will be called to get tokens instead of using
196+ /// static tokens or OAuth credentials. This allows for custom token management
197+ /// such as reading from files, APIs, or other custom sources.
198+ pub fn with_authenticator ( mut self , authenticator : Arc < dyn CustomAuthenticator > ) -> Self {
199+ self . authenticator = Some ( authenticator) ;
200+ self
201+ }
202+
203+ /// Add bearer token to request authorization header.
204+ fn set_bearer_token ( req : & mut Request , token : & str , error_msg : & str ) -> Result < ( ) > {
205+ req. headers_mut ( ) . insert (
206+ http:: header:: AUTHORIZATION ,
207+ format ! ( "Bearer {token}" )
208+ . parse ( )
209+ . map_err ( |e| Error :: new ( ErrorKind :: DataInvalid , error_msg) . with_source ( e) ) ?,
210+ ) ;
211+ Ok ( ( ) )
212+ }
213+
177214 /// Invalidate the current token without generating a new one. On the next request, the client
178215 /// will attempt to generate a new token.
179216 pub ( crate ) async fn invalidate_token ( & self ) -> Result < ( ) > {
@@ -195,18 +232,24 @@ impl HttpClient {
195232
196233 /// Authenticates the request by adding a bearer token to the authorization header.
197234 ///
198- /// This method supports three authentication modes:
235+ /// This method supports four authentication modes (in order of precedence) :
199236 ///
200- /// 1. **No authentication** - Skip authentication when both `credential` and `token` are missing.
201- /// 2. **Token authentication** - Use the provided `token` directly for authentication.
202- /// 3. **OAuth authentication** - Exchange `credential` for a token, cache it, then use it for authentication.
237+ /// 1. **Custom authenticator** - If set, use the custom CustomAuthenticator to get tokens.
238+ /// 2. **Token authentication** - Use the provided static `token` directly.
239+ /// 3. **OAuth authentication** - Exchange `credential` for a token, cache it, then use it.
240+ /// 4. **No authentication** - Skip authentication when none of the above are available.
203241 ///
204- /// When both `credential` and `token` are present, `token` takes precedence.
205- ///
206- /// # TODO: Support automatic token refreshing.
242+ /// When an authenticator is provided, it takes precedence over static tokens and credentials.
207243 async fn authenticate ( & self , req : & mut Request ) -> Result < ( ) > {
244+ // Try authenticator first (highest priority)
245+ if let Some ( authenticator) = & self . authenticator {
246+ let token = authenticator. get_token ( ) . await ?;
247+ Self :: set_bearer_token ( req, & token, "Invalid custom token" ) ?;
248+ return Ok ( ( ) ) ;
249+ }
250+
208251 // Clone the token from lock without holding the lock for entire function.
209- let token = self . token . lock ( ) . await . clone ( ) ;
252+ let token: Option < String > = self . token . lock ( ) . await . clone ( ) ;
210253
211254 if self . credential . is_none ( ) && token. is_none ( ) {
212255 return Ok ( ( ) ) ;
@@ -224,18 +267,7 @@ impl HttpClient {
224267 }
225268 } ;
226269
227- // Insert token in request.
228- req. headers_mut ( ) . insert (
229- http:: header:: AUTHORIZATION ,
230- format ! ( "Bearer {token}" ) . parse ( ) . map_err ( |e| {
231- Error :: new (
232- ErrorKind :: DataInvalid ,
233- "Invalid token received from catalog server!" ,
234- )
235- . with_source ( e)
236- } ) ?,
237- ) ;
238-
270+ Self :: set_bearer_token ( req, & token, "Invalid token received from catalog server!" ) ?;
239271 Ok ( ( ) )
240272 }
241273
0 commit comments