pub struct IntegrationsOauthService { /* private fields */ }Expand description
The one OAuth generation service. Built once (config validated at build — a bad endpoint override or a missing state secret refuses construction), holding the resolved-and-validated endpoints per provider, the Tier-A state signer, and the two ports (credential store + outbound transport) the flow runs through.
Implementations§
Source§impl IntegrationsOauthService
impl IntegrationsOauthService
Sourcepub fn build(
pool: PgPool,
config: IntegrationsOauthConfig,
transport: Arc<dyn OAuthTransport>,
store: Arc<dyn OAuthCredentialStore>,
) -> Result<Self>
pub fn build( pool: PgPool, config: IntegrationsOauthConfig, transport: Arc<dyn OAuthTransport>, store: Arc<dyn OAuthCredentialStore>, ) -> Result<Self>
Validate configuration and construct the service. FAIL CLOSED: an
unusable state_secret_source, a missing public_base, or ANY
endpoint override the guard refuses is a build error — there is no
degraded mode that skips the guard.
Start ONE authorization through the ONE flow. Validates the claimed provider-side identity, replaces any existing row for the scope with a FRESH pending account (terminal statuses are never transitioned out of), mints the PKCE verifier + Tier-A state, and returns the provider authorize URL. Zero store contact on this path (and no company parameter — the module’s rows are unfenced; ADR-0029).
Sourcepub fn callback_page(
&self,
code: &str,
state: &str,
) -> Result<String, OauthError>
pub fn callback_page( &self, code: &str, state: &str, ) -> Result<String, OauthError>
Render the provider redirect target: verify the state (constant-time,
mandatory expiry) and return a minimal HTML page that auto-submits an
invisible POST form carrying code + state to the completion route.
A SAFE METHOD: this writes nothing, mints nothing, transitions nothing
— a GET here cannot change a single row. Garbage / tampered / expired
state → rejection, still zero writes. The form target is RELATIVE, so
no state-supplied (or any other) URL is ever redirected to.
Sourcepub async fn complete(
&self,
company_id: Uuid,
req: CompleteRequest,
) -> Result<CompleteOutcome, OauthError>
pub async fn complete( &self, company_id: Uuid, req: CompleteRequest, ) -> Result<CompleteOutcome, OauthError>
Complete an authorization: re-verify the state, exchange the code at
the VALIDATED token endpoint (PKCE verifier from the pending account),
then run the identity gauntlet — audience == configured client id,
id_token nonce == the nonce minted into the signed state, and the
provider-side identity must match the claimed account_ref. Only
then is the bundle stored (honest expiry, never NULL) and the account
transitioned pending → active. ANY rejection leaves zero writes.
company_id is the credential-store scope key (the legacy tenancy
twin, ADR-0029) — the module’s own rows are unfenced.
Sourcepub async fn disconnect(
&self,
company_id: Uuid,
account_id: Uuid,
) -> Result<(), OauthError>
pub async fn disconnect( &self, company_id: Uuid, account_id: Uuid, ) -> Result<(), OauthError>
Disconnect: revoke the scope’s credential through the port (a scope
that never had one revokes cleanly), then transition the account to
its terminal revoked. Idempotent for an already-revoked account.
company_id is the credential-store scope key (the legacy tenancy
twin, ADR-0029).
Sourcepub async fn status(
&self,
account_id: Uuid,
) -> Result<AccountStatus, OauthError>
pub async fn status( &self, account_id: Uuid, ) -> Result<AccountStatus, OauthError>
The account’s metadata (never secret material — none exists on the row). No company parameter: the read is ID-only and the decorator’s org fence scopes it (ADR-0029).
Sourcepub async fn refresh_due(
&self,
company_id: Uuid,
) -> Result<RefreshSummary, OauthError>
pub async fn refresh_due( &self, company_id: Uuid, ) -> Result<RefreshSummary, OauthError>
Refresh every due account for ONE credential-store scope (company_id
— the legacy tenancy twin, ADR-0029: the module’s own tables are
unfenced, but the store across the port is still company-scoped, so
the host enumerates the scopes it serves and names one here). Each
account is claimed FOR UPDATE SKIP LOCKED in its OWN short
transaction with the lock held through processing — concurrent runners
take disjoint accounts — and commits independently (one provider
outage rolls back exactly its own account). The transaction binds the
composing service’s ambient org scope when one is resolved, so a
decorated deployment’s fence applies to the claim and the mirror
updates. invalid_grant expires the account (the user must
reconnect); the credential is left to the store’s lazy expiry. Re-runs
converge: a bundle the store already rotated is only re-mirrored,
never re-exchanged.
Sourcepub async fn ensure_fresh(
&self,
company_id: Uuid,
account_id: Uuid,
) -> Result<AccountStatus, OauthError>
pub async fn ensure_fresh( &self, company_id: Uuid, account_id: Uuid, ) -> Result<AccountStatus, OauthError>
Request-path lazy refresh (refresh-on-use): if THIS account is inside
the refresh window, run the same single-account refresh the sweep
runs. Returns the account’s metadata either way. company_id is the
credential-store scope key (the legacy tenancy twin, ADR-0029).
Auto Trait Implementations§
impl !RefUnwindSafe for IntegrationsOauthService
impl !UnwindSafe for IntegrationsOauthService
impl Freeze for IntegrationsOauthService
impl Send for IntegrationsOauthService
impl Sync for IntegrationsOauthService
impl Unpin for IntegrationsOauthService
impl UnsafeUnpin for IntegrationsOauthService
Blanket Implementations§
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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§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