Skip to main content

OAuthTokenStore

Struct OAuthTokenStore 

Source
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

Source

pub fn new() -> Self

An empty in-process store, minting nothing yet.

Source

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.

Source

fn lifetimes() -> Lifetimes

This module’s own ACCESS_TTL and REFRESH_TTL, as the grant store takes them.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn revoke_all(&self)

Ends every grant this store holds, immediately — what the e-stop’s Revoke setting throws.

Trait Implementations§

Source§

impl Default for OAuthTokenStore

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,