dioxus_clerk/server/context.rs
1//! Server verification outcome reader for Dioxus fullstack task context.
2//!
3//! Inside a Dioxus server function, call [`current_auth`],
4//! [`current_auth_opt`], or [`current_outcome`] to read the Server verification
5//! outcome placed on the request by [`crate::server::ClerkAuthLayer`].
6//! `ClerkError` converts into Dioxus' `ServerFnError`, so server functions can
7//! use `current_auth()?` directly when returning `Result<_, ServerFnError>`.
8
9use crate::core::{ClerkAuth, ClerkError, VerificationOutcome};
10use axum::http::StatusCode;
11use dioxus_fullstack_core::{FullstackContext, HttpError, ServerFnError};
12
13/// Returns the current Server verification outcome, if a `FullstackContext`
14/// exists and carries one.
15pub fn current_outcome() -> Option<VerificationOutcome> {
16 FullstackContext::current().and_then(|ctx| ctx.extension::<VerificationOutcome>())
17}
18
19/// Returns the verification outcome carried by the current request context,
20/// or an error when no `FullstackContext` exists at all.
21fn required_context_outcome() -> Result<Option<VerificationOutcome>, ClerkError> {
22 let ctx = FullstackContext::current().ok_or(ClerkError::NoServerContext)?;
23 let outcome = ctx.extension::<VerificationOutcome>();
24 if outcome.is_none() {
25 // A context without an outcome means no ClerkAuthLayer ran for this
26 // request; otherwise indistinguishable from "everyone is anonymous".
27 super::extractor::log_missing_layer();
28 }
29 Ok(outcome)
30}
31
32/// Returns verified auth from the current context, or `None` for anonymous,
33/// invalid, or absent outcomes.
34///
35/// This is the optional counterpart to [`current_auth`]: an anonymous or
36/// invalid request yields `Ok(None)` here rather than an error, so a handler
37/// that serves both signed-in and anonymous callers can branch on the `Option`.
38/// Both readers still fail closed on an unavailable verifier.
39///
40/// Errors with [`ClerkError::NoServerContext`] outside a server function or
41/// SSR scope, and with [`ClerkError::JwksUnavailable`] when verification
42/// infrastructure was unavailable.
43pub fn current_auth_opt() -> Result<Option<ClerkAuth>, ClerkError> {
44 match required_context_outcome()? {
45 Some(VerificationOutcome::Unavailable) => Err(jwks_unavailable()),
46 Some(VerificationOutcome::Valid(auth)) => Ok(Some(auth)),
47 _ => Ok(None),
48 }
49}
50
51/// Returns verified auth from the current context.
52///
53/// Errors with [`ClerkError::Unauthenticated`] for anonymous or invalid
54/// credentials, [`ClerkError::TokenExpired`] when the presented token was past
55/// its expiry, [`ClerkError::JwksUnavailable`] when verification
56/// infrastructure was unavailable, and [`ClerkError::NoServerContext`] outside
57/// a server function or SSR scope.
58///
59/// Only `Expired` maps to [`ClerkError::TokenExpired`]; other invalid-token
60/// reasons (including `NotYetValid`) collapse into
61/// [`ClerkError::Unauthenticated`], because expiry is the one case callers
62/// can meaningfully act on (prompt a re-authentication).
63pub fn current_auth() -> Result<ClerkAuth, ClerkError> {
64 match required_context_outcome()? {
65 Some(VerificationOutcome::Valid(auth)) => Ok(auth),
66 Some(VerificationOutcome::Invalid(reason)) => Err(reason.into()),
67 Some(VerificationOutcome::Unavailable) => Err(jwks_unavailable()),
68 _ => Err(ClerkError::Unauthenticated),
69 }
70}
71
72/// The outcome extension does not carry failure details; the verification
73/// layer logs the underlying cause where the fetch fails.
74fn jwks_unavailable() -> ClerkError {
75 ClerkError::JwksUnavailable(
76 "verification infrastructure was unavailable for this request".into(),
77 )
78}
79
80impl From<ClerkError> for ServerFnError {
81 fn from(value: ClerkError) -> Self {
82 let status = clerk_error_status(&value);
83 HttpError::new(status, value.to_string()).into()
84 }
85}
86
87fn clerk_error_status(error: &ClerkError) -> StatusCode {
88 match error {
89 ClerkError::Unauthenticated | ClerkError::TokenExpired => StatusCode::UNAUTHORIZED,
90 ClerkError::JwksUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
91 _ => StatusCode::INTERNAL_SERVER_ERROR,
92 }
93}