pub struct OAuthTokenStore {
grants: Arc<dyn OAuthGrantStore>,
}Expand description
OAuth access/refresh token issuance and rotation.
The family bookkeeping — which token digests are live, whose account they
name, when they stop working, and the reuse rule that ends a family — is
didbot_pds::oauth’s, so that a deployment with a --data directory
carries its grants in the same write-ahead log its accounts and agent
tokens already live in. What this type holds is the part that is this
crate’s: PKCE, DPoP proof verification, and the scope grammar.
Fields§
§grants: Arc<dyn OAuthGrantStore>Implementations§
Source§impl OAuthTokenStore
impl OAuthTokenStore
Sourcepub fn with_grants(grants: Arc<dyn OAuthGrantStore>) -> Self
pub fn with_grants(grants: Arc<dyn OAuthGrantStore>) -> Self
A store over the grants a deployment supplied — the durable one for a
--data run; see didbot_pds::Durable::oauth_grants.
Sourcefn lifetimes() -> Lifetimes
fn lifetimes() -> Lifetimes
This module’s own ACCESS_TTL and REFRESH_TTL, as the grant
store takes them.
Sourcefn tokens(minted: MintedGrant, scope: ScopeSet) -> OAuthTokens
fn tokens(minted: MintedGrant, scope: ScopeSet) -> OAuthTokens
The wire shape of a minted pair, carrying scope: what the pair may
do as it is issued.
Sourcepub fn exchange_code(
&self,
code_store: &dyn AuthorizationCodeStore,
dpop: &dyn DpopVerifier,
admission: &dyn TokenAdmission,
ceiling: &dyn ScopePolicy,
code: &str,
redirect_uri: &str,
code_verifier: &str,
client_id: &str,
client: Option<&AuthenticatedClient>,
proof: &VerifiedProof,
) -> Result<OAuthTokens, TokenError>
pub fn exchange_code( &self, code_store: &dyn AuthorizationCodeStore, dpop: &dyn DpopVerifier, admission: &dyn TokenAdmission, ceiling: &dyn ScopePolicy, code: &str, redirect_uri: &str, code_verifier: &str, client_id: &str, client: Option<&AuthenticatedClient>, proof: &VerifiedProof, ) -> Result<OAuthTokens, TokenError>
Exchanges an authorization code for a fresh access/refresh pair,
binding both to the DPoP key proof proved possession of.
proof is the request’s DPoP proof, already verified: the route
checks it before anything in the body is spent, so a nonce challenge
leaves the code for the client’s retry. It costs dpop a
replay-window entry once the code checks out. Its key must be the one
the sign-in’s push was signed with, or the code is spent and nothing
is issued, as the reference provider orders it.
client is the confidential client the request authenticated as,
if it carried an assertion; see this module’s doc for what a code
needs of it.
admission and ceiling are asked last, once the caller has proven
it holds the code and the key. A client admission refuses, or one the
ceiling now grants nothing, gets no token, and the code is spent
either way.
Sourcepub fn refresh(
&self,
dpop: &dyn DpopVerifier,
admission: &dyn TokenAdmission,
ceiling: &dyn ScopePolicy,
refresh_token: &str,
client: Option<&AuthenticatedClient>,
proof: &VerifiedProof,
) -> Result<OAuthTokens, TokenError>
pub fn refresh( &self, dpop: &dyn DpopVerifier, admission: &dyn TokenAdmission, ceiling: &dyn ScopePolicy, refresh_token: &str, client: Option<&AuthenticatedClient>, proof: &VerifiedProof, ) -> Result<OAuthTokens, TokenError>
Rotates a refresh token. A refresh token presented after it was already rotated away revokes its whole family: a replayed refresh token is indistinguishable from a race with the legitimate client, and the safe response to either is to stop trusting the family.
admission, ceiling and client are asked only about a refresh
token that would rotate, so a replayed one still ends its family. A
client admission refuses, or one the ceiling now grants nothing, or
one that did not authenticate the way its login began (see this
module’s doc), keeps its family, unrotated: the refresh token expires
on its own schedule, and works again if the refusal lifts first.
proof is the request’s DPoP proof, already verified, as
OAuthTokenStore::exchange_code takes one.
Sourcepub fn validate_access(
&self,
access_token: &str,
thumbprint: &DpopThumbprint,
ceiling: &dyn ScopePolicy,
) -> Option<AccessGrant>
pub fn validate_access( &self, access_token: &str, thumbprint: &DpopThumbprint, ceiling: &dyn ScopePolicy, ) -> Option<AccessGrant>
The account an OAuth access token authenticates as, and the client
it was issued to, or None.
thumbprint is not optional: it is the RFC 7638 thumbprint of the
key a caller has already proven possession of — by verifying a fresh
DPoP proof against the request this token accompanies, the same way
OAuthTokenStore::exchange_code and OAuthTokenStore::refresh
take a VerifiedProof rather than a bare parameter. A caller
cannot get a thumbprint any other way than verifying a proof, so
this signature makes “authenticate a bare
token string with no proof of possession” a type error rather than a
documented trap — see plan/adversarial.md’s DPoP-binding findings,
which this signature exists to close.
Refuses (returns None) on an unknown, stale, revoked, or
already-rotated-away access token — and whenever thumbprint does
not match the key this token’s family was minted bound to, which is
the entire point of DPoP: a copied bearer string is worthless to
whoever copied it, because their proof is signed by a different key
with a different thumbprint. Also None for a grant whose stored
client id policy could not match; see stored_client_id.
The scope answered is in_force under ceiling, never what was
granted when the token was issued.
Sourcepub fn revoke_account(&self, did: &str) -> Result<usize, Unkept>
pub fn revoke_account(&self, did: &str) -> Result<usize, Unkept>
Ends every grant this store holds for did, immediately.
What a deletion calls, through
didbot_pds::Registry::set_account_revoke_hook: an OAuth grant
names an account, and a grant that answers for an account this
deployment no longer has is a credential with nothing behind it.
A family is ended the same way a detected refresh reuse ends one — so an access token for a deleted account stops validating on the next call and its refresh token has nothing left to rotate into — and a durable store writes the revocation down before this returns. Answers how many families went.
§Errors
Unkept when the grant store could not write the ending down. The
logins end in this process either way, and a restart may bring them
back.
Sourcepub fn live(&self) -> Vec<Grant>
pub fn live(&self) -> Vec<Grant>
Every live grant family this store holds, for the operations
dashboard’s per-app view; see
didbot_pds::oauth::OAuthGrantStore::live.
Sourcepub fn revoke_client(
&self,
client_id: &str,
did: Option<&str>,
) -> Result<Vec<Grant>, Unkept>
pub fn revoke_client( &self, client_id: &str, did: Option<&str>, ) -> Result<Vec<Grant>, Unkept>
Ends every grant this store holds for client_id, or for
client_id on did alone, immediately. Answers the logins that
went, each naming its account.
What an operator reaches for when an application’s keys or tokens leak: the logins it holds end for good, and the application itself stays as allowed as it was. Denying it is a separate, reversible policy edit.
§Errors
Unkept when the grant store could not write the ending down. The
logins end in this process either way, and a restart may bring them
back.
Sourcepub fn revoke_all(&self)
pub fn revoke_all(&self)
Ends every grant this store holds, immediately — what the e-stop’s Revoke setting throws.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for OAuthTokenStore
impl !RefUnwindSafe for OAuthTokenStore
impl Send for OAuthTokenStore
impl Sync for OAuthTokenStore
impl Unpin for OAuthTokenStore
impl UnsafeUnpin for OAuthTokenStore
impl !UnwindSafe for OAuthTokenStore
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more