Skip to main content

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}