pub struct RefreshTokenSource<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> { /* private fields */ }Expand description
A renewing TokenSource: mints access tokens via the OAuth refresh_token
grant and persists the rotated refresh token through a TokenStore.
Depends on the PasAuthPort port (not the concrete AuthClient), so it
is unit-tested against an in-memory adapter with no HTTP. Wire it into a
ppoppo_sdk_core::token_cache::TokenCache so the cache renews on lapse.
§The scope parameter — a per-refresh check, not a construction-time one
Sc is the tier this credential is expected to carry. Every refresh
validates the server’s scope echo against it (ensure_covers) before
handing the access token back, so a grant that narrows mid-session —
which OAuth 2.1 §1.3.2 explicitly permits — surfaces as a terminal error
rather than as an unexplained 403 from the resource server later.
This is why the check lives here and not in a wrapper:
TokenSource::fetch_token returns only (String, Duration), so nothing
composed around this type can ever see the scope echo. The seam has to be
inside.
That also makes ScopedTokenSource<Sc> a stronger witness than a
construction-time check would be. The marker holds for every instance
because the guarantee is intrinsic to use: any token this source yields
has just been checked. new therefore needs no proof from its
caller — there is no window in which an unchecked token escapes.
Implementations§
Source§impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> RefreshTokenSource<P, T, Sc>
impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> RefreshTokenSource<P, T, Sc>
Sourcepub fn new(auth: P, store: T) -> Self
pub fn new(auth: P, store: T) -> Self
auth performs the refresh grant (typically an AuthClient carrying
the token endpoint + client_id + RFC 8707 resource); store holds the
durable refresh token (seed it with the initial token first).
Sc is usually inferred from the surrounding binding; give it
explicitly (RefreshTokenSource::<_, _, Notify>::new(..)) where it is
not.
Most consumers do not call this directly — AuthClient cannot be
constructed outside this crate, so a native client obtains a ready
source from
NativeAuthFlow::token_source
or NativeAuthFlow::complete. This
constructor is for consumers supplying their own PasAuthPort.
Trait Implementations§
Source§impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> Debug for RefreshTokenSource<P, T, Sc>
impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> Debug for RefreshTokenSource<P, T, Sc>
impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> ScopedTokenSource<Sc> for RefreshTokenSource<P, T, Sc>
The witness: every token this source yields has passed ensure_covers
against Sc. See the type’s docs for why the guarantee attaches to use
rather than construction.
Source§impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> TokenSource for RefreshTokenSource<P, T, Sc>
impl<P: PasAuthPort, T: TokenStore, Sc: ConsentScopes> TokenSource for RefreshTokenSource<P, T, Sc>
Source§fn fetch_token<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(String, Duration), TokenCacheError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn fetch_token<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(String, Duration), TokenCacheError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
TokenCacheConfig::refresh_skew from the TTL
before storing the expiry, so the token is treated as stale slightly
before the server considers it expired.