pub struct DingtalkAuthPlugin {
app_key: String,
app_secret: String,
code_header: String,
code_query: String,
token_url: String,
userinfo_url: String,
set_userinfo_header: bool,
timeout: Duration,
ssl_verify: bool,
resources: Arc<PluginResources>,
token_cache: Mutex<Option<(String, Instant)>>,
session: Option<DingtalkSession>,
}Expand description
Authenticates requests by resolving a DingTalk authorization code to a
DingTalk user via the OAuth accessToken + getuserinfo APIs.
Fields§
§app_key: String§app_secret: String§code_header: StringLowercased header the code is read from first.
code_query: StringQuery parameter the code falls back to.
token_url: String§userinfo_url: String§set_userinfo_header: bool§timeout: Duration§ssl_verify: bool§resources: Arc<PluginResources>§token_cache: Mutex<Option<(String, Instant)>>In-process cache of the app-level access token: (token, fetched_at).
session: Option<DingtalkSession>Session-mode settings; None keeps the stateless token-validation
behavior (the pre-existing, backward-compatible default).
Implementations§
Source§impl DingtalkAuthPlugin
impl DingtalkAuthPlugin
Sourcepub fn from_config(
config: &HashMap<String, Value>,
resources: &Arc<PluginResources>,
) -> Result<Self, String>
pub fn from_config( config: &HashMap<String, Value>, resources: &Arc<PluginResources>, ) -> Result<Self, String>
Builds the plugin from node config.
Accepted keys:
app_key(string, required): DingTalk application key.app_secret(string, required): DingTalk application secret.code_header(string, default"X-DingTalk-Code"): header the authorization code is read from first (matched case-insensitively).code_query(string, default"code"): query parameter the code falls back to when the header is absent.access_token_url(string, default DingTalk’soauth2/accessToken).userinfo_url(string, default DingTalk’sv2/user/getuserinfo).set_userinfo_header(bool, defaulttrue): when true the resolved userinfo JSON is base64-encoded into theX-Userinforequest header for the upstream.timeout(integer ms, default6000): per-callout timeout.ssl_verify(bool, defaulttrue): verify DingTalk’s TLS certificate.
Session-mode keys (present ⇒ session mode is enabled — see the module docs):
session_secret(string) orsession.secret(string): signing/ encryption secret for the session cookie. Setting it turns on the session flow.session.cookie.name(string, default"dingtalk_session").session.cookie.path(string, default"/").session.cookie.lifetime(u64 seconds, default86400; APISIX’scookie_expires_in).session.storage/session.store: server-side session backend (seeserver_session::parse_backend).redirect_uri(string, required in session mode): where to 302 a browser that has neither a valid session nor a code.
secret_fallbacks (APISIX multi-secret rotation) is not accepted —
see the module docs.
type: dingtalk-auth
config:
app_key: ${DINGTALK_APP_KEY}
app_secret: ${DINGTALK_APP_SECRET}
code_header: X-DingTalk-Code
session:
secret: ${DINGTALK_SESSION_SECRET}
redirect_uri: https://login.example.com/startSourcefn extract_code(&self, ctx: &Context) -> Option<String>
fn extract_code(&self, ctx: &Context) -> Option<String>
Reads the authorization code from the configured header, falling back to the query parameter.
Sourceasync fn access_token(&self) -> Result<String, DingtalkError>
async fn access_token(&self) -> Result<String, DingtalkError>
Returns a valid access token, using the in-process cache when fresh and fetching a new one from DingTalk otherwise.
Sourceasync fn fetch_userinfo(
&self,
access_token: &str,
code: &str,
) -> Result<Value, DingtalkError>
async fn fetch_userinfo( &self, access_token: &str, code: &str, ) -> Result<Value, DingtalkError>
Exchanges the code for DingTalk userinfo using access_token.
Sourcefn reject(
ctx: Context,
message: &str,
) -> Result<PluginOutput, PluginExecutionError>
fn reject( ctx: Context, message: &str, ) -> Result<PluginOutput, PluginExecutionError>
Builds the 401 rejection and exits on the denied port. Reserved
for a deliberate denial — a missing code, or DingTalk actively
rejecting the code/token.
Sourcefn upstream_error(
ctx: Context,
message: &str,
) -> Result<PluginOutput, PluginExecutionError>
fn upstream_error( ctx: Context, message: &str, ) -> Result<PluginOutput, PluginExecutionError>
Builds a genuine infrastructure-failure Err for a DingTalk callout
that failed outright (network error, non-200, unparseable body) —
unlike reject, the node could not do its job rather than DingTalk
deliberately refusing the code.
Sourcefn store_error(ctx: Context, e: StoreError) -> PluginExecutionError
fn store_error(ctx: Context, e: StoreError) -> PluginExecutionError
Session-store outage: 503 through the error port. Deliberately NOT 401 — a store outage is not “unauthenticated”.
Sourcefn redirect(
ctx: Context,
location: &str,
set_cookies: Vec<String>,
) -> Result<PluginOutput, PluginExecutionError>
fn redirect( ctx: Context, location: &str, set_cookies: Vec<String>, ) -> Result<PluginOutput, PluginExecutionError>
Builds a 302 early-exit carrying the prepared response, and exits on
the redirect port. Wire the node’s redirect edge to client.in so
this reaches the browser.
Sourcefn session_attrs<'a>(
session: &'a DingtalkSession,
ctx: &Context,
) -> CookieAttrs<'a>
fn session_attrs<'a>( session: &'a DingtalkSession, ctx: &Context, ) -> CookieAttrs<'a>
Cookie attributes for the session cookie: HttpOnly, SameSite=Lax,
and Secure only over HTTPS (so plain-HTTP dev works).
Sourceasync fn read_session(
&self,
ctx: &Context,
session: &DingtalkSession,
) -> Result<(Option<Value>, Option<String>), StoreError>
async fn read_session( &self, ctx: &Context, session: &DingtalkSession, ) -> Result<(Option<Value>, Option<String>), StoreError>
Reads the session cookie via the configured backend.
Ok(Some(userinfo)) = a valid session; Ok(None) = no session (no
cookie, unopenable/expired/tampered value, or a payload that failed to
decode as JSON — in which case the session was also destroyed so a
stale entry does not linger); Err = store outage (503 via
DingtalkAuthPlugin::store_error), never a silent re-login.
When the payload was undecodable, the returned delete-cookie
Set-Cookie value is included so the caller can forward it on
whatever response it ultimately builds.
Sourceasync fn execute_session(
&self,
ctx: Context,
session: &DingtalkSession,
) -> Result<PluginOutput, PluginExecutionError>
async fn execute_session( &self, ctx: Context, session: &DingtalkSession, ) -> Result<PluginOutput, PluginExecutionError>
Session-mode flow: read session → callback (code) → begin login.
Trait Implementations§
Source§impl Plugin for DingtalkAuthPlugin
impl Plugin for DingtalkAuthPlugin
Source§fn plugin_type(&self) -> &str
fn plugin_type(&self) -> &str
Source§fn execute<'life0, 'async_trait>(
&'life0 self,
ctx: Context,
) -> Pin<Box<dyn Future<Output = Result<PluginOutput, PluginExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn execute<'life0, 'async_trait>(
&'life0 self,
ctx: Context,
) -> Pin<Box<dyn Future<Output = Result<PluginOutput, PluginExecutionError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn reads_response_body(&self) -> bool
fn reads_response_body(&self) -> bool
context.response.body. Read moreSource§fn cache_target(&self) -> Option<CacheTarget>
fn cache_target(&self) -> Option<CacheTarget>
proxy-cache half. Read moreAuto Trait Implementations§
impl !Freeze for DingtalkAuthPlugin
impl !RefUnwindSafe for DingtalkAuthPlugin
impl !UnwindSafe for DingtalkAuthPlugin
impl Send for DingtalkAuthPlugin
impl Sync for DingtalkAuthPlugin
impl Unpin for DingtalkAuthPlugin
impl UnsafeUnpin for DingtalkAuthPlugin
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