1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Stackure is the Rust SDK for the Stackure authentication API.
//!
//! Stackure provides passwordless B2B authentication. This SDK wraps the
//! public API behind seven free functions and a tower middleware.
//!
//! # Quickstart
//!
//! Protect an axum app:
//!
//! ```no_run
//! # use axum::{Router, routing::get};
//! # let app: Router = Router::new().route("/admin", get(|| async {}));
//! let app = app.layer(stackure::auth(APP_ID, &["can_approve_invoice"]));
//! # const APP_ID: &str = "7f3c1a2e-9b4d-4e6f-8a1b-2c3d4e5f6071";
//! ```
//!
//! `APP_ID` is the app's UUID as registered in Stackure. The layer works in
//! any tower stack — axum, tonic, or hyper.
//!
//! Access the authenticated user inside a handler:
//!
//! ```no_run
//! # fn example(parts: &http::request::Parts) {
//! let user = stackure::user_from_request(parts);
//! # }
//! ```
//!
//! Manual verification without middleware:
//!
//! ```no_run
//! # async fn example(parts: &http::request::Parts) {
//! # const APP_ID: &str = "7f3c1a2e-9b4d-4e6f-8a1b-2c3d4e5f6071";
//! let result = stackure::verify(APP_ID, parts, &[]).await;
//! if result.authenticated {
//! // use result.user
//! }
//! # }
//! ```
//!
//! Send a magic-link email:
//!
//! ```no_run
//! # async fn example() {
//! # const APP_ID: &str = "7f3c1a2e-9b4d-4e6f-8a1b-2c3d4e5f6071";
//! let response = stackure::send_magic_link("user@example.com", Some(APP_ID)).await;
//! # }
//! ```
//!
//! Log the user out:
//!
//! ```no_run
//! # fn example(parts: &http::request::Parts) -> http::Response<axum::body::Body> {
//! stackure::logout(parts)
//! # }
//! ```
//!
//! # Sign-in handoff
//!
//! Stackure's session cookie is scoped to the Stackure host and is never
//! visible to your app. After a successful magic-link sign-in, Stackure hands
//! the browser back to the app's registered URL with a `session_token` POST
//! form field: an app-scoped session token valid only for this app, accepted
//! by the validate endpoint for your app ID and never by Stackure itself.
//!
//! The [`auth`] layer consumes it automatically: it validates the token, stores
//! it in a cookie on your own domain and redirects to the same URL as a GET.
//! Invalid tokens are ignored, and handoff bodies over 4 KB are ignored (treated
//! as no token).
//!
//! # Session binding
//!
//! Sessions are not bound to the browser's user agent or IP address. The SDK
//! still forwards the original `User-Agent` and `X-Forwarded-For` on every
//! validation call, but they are informational only; validation does not
//! depend on them.
//!
//! Every request with a session token is validated against Stackure, so revoking
//! a session takes effect immediately. Requests without a well-formed token get
//! the sign-in URL without a Stackure call.
//!
//! # Content negotiation
//!
//! The [`auth`] layer inspects the `Accept` header. Browser requests (`Accept:
//! text/html`) redirect to the sign-in URL on 401. API requests (`Accept:
//! application/json`) receive a JSON error body.
//!
//! # Configuration
//!
//! `STACKURE_APP_SECRET` must be set to the app secret shown when the app was
//! registered (or last rotated) in Stackure. It is sent as the `X-App-Secret`
//! header on every call; the first call that actually reaches Stackure fails
//! with [`StackureError::Validation`] when it is unset. `STACKURE_BASE_URL`
//! overrides the API host (default `https://stackure.com`).
//!
//! A newly registered app is not usable by anyone, even its creator, until it
//! is shared with the organization or assigned to a team in Stackure. Do that
//! before testing sign-in.
//!
//! Every SDK call has one 2-second wall-clock deadline covering connect,
//! headers, body and the single retry. The SDK retries once, after 500 ms, on
//! a 5xx response or a connection failure, and only if more than 500 ms of the
//! deadline remain. A timeout, including while reading the body, surfaces as
//! [`StackureError::Timeout`] and is never retried.
//!
//! # Errors
//!
//! Every function except [`verify`] returns [`StackureError`]. Match on the
//! variant, or call [`StackureError::code`] for the same lowercase category
//! string the other Stackure SDKs expose as `.code`.
pub use ;
pub use StackureError;
pub use ;
pub use ;