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
//! Stackure is the Rust SDK for the Stackure authentication API.
//!
//! Stackure provides passwordless B2B authentication. This SDK wraps the
//! public API behind five 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`, either
//! as a POST form field or as a query parameter.
//!
//! The [`auth`] layer consumes both automatically: it stores the token in a
//! cookie on your own domain and redirects to the same URL with the parameter
//! stripped, so the token does not linger in the address bar.
//!
//! # Session binding
//!
//! Stackure binds each session to the browser's user agent and IP address.
//! Because the SDK validates from your server rather than the browser, it
//! forwards the original `User-Agent` and `X-Forwarded-For` on every
//! validation call. Your app must therefore see the real client IP: if it sits
//! behind a proxy or CDN, ensure that layer sets `X-Forwarded-For` correctly.
//!
//! Every request is validated against Stackure, so revoking a session takes
//! effect immediately.
//!
//! # 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
//!
//! The SDK has no configuration API. Point it at a non-production environment
//! by setting the `STACKURE_BASE_URL` environment variable before the first
//! call.
//!
//! Retry-on-5xx (one retry after 500ms) and the 2-second request timeout are
//! hard-coded. Timeouts are 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 ;