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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Session auth for Dioxus fullstack apps on SurrealDB: sessions stored in
//! the database, a signed-in user on every request, and a guard that denies
//! by default.
//!
//! # The guard
//!
//! Every request needs a signed-in user unless it is for:
//!
//! - a static asset or `/.well-known/` file ([`is_static_asset`]),
//! - a page marked `#[public]` in the app's [`PublicRoutes`] enum, or
//! - a server function marked [`public`]:
//!
//! ```ignore
//! /// The splash asks this before it knows whether anyone is signed in.
//! #[g3_auth::public]
//! #[get("/api/v1/is_signed_in", ctx: SessionContext)]
//! pub async fn is_signed_in() -> Result<bool> {
//! Ok(!ctx.session_user.anonymous)
//! }
//! ```
//!
//! Forgetting `#[public]` is the safe mistake: a signed-out caller gets a
//! `401`, which the page can see and act on. A signed-out page load is
//! redirected to the splash instead ([`is_document_navigation`]).
//!
//! Pages are marked the same way, on the route enum:
//!
//! ```ignore
//! #[derive(Clone, Routable, PartialEq, PublicRoutes)]
//! enum Route {
//! #[redirect("/:..segments", |segments: Vec<String>| Route::Splash {})]
//! #[public]
//! #[route("/")]
//! Splash {},
//! #[nest("/games/:game_id")]
//! #[public]
//! #[route("/join")]
//! JoinGame { game_id: String },
//! #[end_nest]
//! #[route("/home")]
//! Home {},
//! }
//! ```
//!
//! # Setup
//!
//! ```toml
//! [dependencies]
//! g3-auth = "0.1"
//!
//! [features]
//! server = ["dioxus/server", "g3-auth/server"]
//! ```
//!
//! Load [`SESSIONS_SCHEMA`] into the database (or copy it into the app's
//! schema), describe the account table with [`AuthUser`], and build the
//! router, innermost layer first:
//!
//! ```ignore
//! pub enum AppUser {}
//! impl AuthUser for AppUser {}
//!
//! // Panics at startup if the splash isn't `#[public]`: the redirect would loop.
//! let guard = AuthGuard::for_routes(Route::Splash {});
//!
//! let session_store = SessionStore::new(
//! Some(SurrealSessionPool::new(Arc::clone(&db))),
//! SessionConfig::default().with_secure(!cfg!(debug_assertions)),
//! )
//! .await?;
//!
//! dioxus::server::router(App)
//! .layer(Extension(Arc::clone(&db)))
//! .layer(from_fn_with_state(guard, require_session::<AppUser, Client>))
//! .layer(AuthSessionLayer::<AppUser, Client>::new(Some(Arc::clone(&db))))
//! .layer(SessionLayer::new(session_store))
//! ```
//!
//! Only the `server` feature pulls in the server side (axum, axum_session,
//! SurrealDB). Without it, [`public`] and [`PublicRoutes`] still compile, so
//! the same code builds for web and mobile.
//!
//! # What the guard does not cover
//!
//! A server function called during server-side rendering runs directly,
//! without any middleware, so the guard only applies to HTTP requests. A
//! public page that renders a private server function would run it for a
//! signed-out visitor. Functions that act on "the current user" should still
//! check `session_user.anonymous` rather than trust that they were guarded.
pub use ;
pub use ;
pub use PublicRoutes;
pub use SurrealSessionPool;
pub use ;
/// The `sessions` table [`SurrealSessionPool`] reads and writes.
pub const SESSIONS_SCHEMA: &str = include_str!;
pub use public;
/// Makes the session survive a relaunch on a phone or desktop app. Call it
/// first in `main`, before launch and before anything can make a request;
/// it does nothing on the web and the server, so no `cfg` is needed:
///
/// ```ignore
/// fn main() {
/// g3_auth::init();
/// dioxus::launch(App);
/// }
/// ```
///
/// A native HTTP client has no browser cookie jar. Without a persistent one,
/// sign-in succeeds, the app works, and the next launch finds nobody signed in
/// (a guest, who cannot sign back in, loses the account). Needs the app's
/// `mobile` or `desktop` feature to enable `g3-auth/mobile` or
/// `g3-auth/desktop`.
/// Support for the code the macros generate. Not public API.