orbital_shell/auth_models.rs
1//! Frontend-facing authentication session models.
2//!
3//! Orbital keeps browser-visible auth state intentionally small: enough information
4//! to personalize the UI, gate pages, and show account status without exposing the
5//! full backend user model. These types are the shared contract between the SSR
6//! session loader and the client-side component tree.
7//!
8//! ## Example
9//!
10//! ```rust
11//! use orbital::{AnonymousUser, AuthSession, AuthenticatedUser};
12//!
13//! let anonymous = AuthSession::Anonymous(AnonymousUser::default());
14//! assert!(!anonymous.is_authenticated());
15//! assert_eq!(anonymous.display_label(), "Guest");
16//!
17//! let authenticated = AuthSession::Authenticated(AuthenticatedUser {
18//! user_id: "user-123".to_string(),
19//! email: Some("alex@example.com".to_string()),
20//! display_name: Some("Alex".to_string()),
21//! avatar_url: None,
22//! roles: vec!["admin".to_string()],
23//! email_verified: true,
24//! });
25//!
26//! assert!(authenticated.is_authenticated());
27//! assert_eq!(authenticated.display_label(), "Alex");
28//! ```
29
30use serde::{Deserialize, Serialize};
31
32/// Represents the current authentication session for the frontend.
33///
34/// UI code generally branches on this enum to decide whether to render anonymous onboarding, authenticated content, or additional account verification prompts.
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub enum AuthSession {
37 /// No authenticated user is present.
38 Anonymous(AnonymousUser),
39 /// An authenticated user is available with profile information.
40 Authenticated(AuthenticatedUser),
41}
42
43impl Default for AuthSession {
44 fn default() -> Self {
45 Self::Anonymous(AnonymousUser::default())
46 }
47}
48
49impl AuthSession {
50 /// Returns `true` when the session represents an authenticated user.
51 pub fn is_authenticated(&self) -> bool {
52 matches!(self, Self::Authenticated(_))
53 }
54
55 /// Convenience accessor for the authenticated user, if present.
56 pub fn user(&self) -> Option<&AuthenticatedUser> {
57 match self {
58 Self::Authenticated(user) => Some(user),
59 Self::Anonymous(_) => None,
60 }
61 }
62
63 /// Provides a display-friendly identifier for UI surfaces.
64 pub fn display_label(&self) -> String {
65 match self {
66 Self::Authenticated(user) => user
67 .display_name
68 .as_ref()
69 .or(user.email.as_ref())
70 .cloned()
71 .unwrap_or_else(|| user.user_id.clone()),
72 Self::Anonymous(_) => "Guest".to_string(),
73 }
74 }
75}
76
77/// Additional context for an anonymous visitor.
78#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
79pub struct AnonymousUser {
80 /// Optional explanation for why the user is anonymous (e.g. session expired).
81 pub reason: Option<String>,
82}
83
84/// Core identity and profile details for an authenticated user.
85///
86/// This is the browser-safe subset of user information commonly needed by page headers, permission gates, account menus, and audit-friendly UI.
87#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
88pub struct AuthenticatedUser {
89 /// Stable identifier for the user (e.g. database key).
90 pub user_id: String,
91 /// Email address when available.
92 pub email: Option<String>,
93 /// Preferred display name.
94 pub display_name: Option<String>,
95 /// Optional avatar image URL.
96 pub avatar_url: Option<String>,
97 /// Ordered list of role or capability identifiers.
98 pub roles: Vec<String>,
99 /// Whether the account's primary email is verified.
100 pub email_verified: bool,
101}