Skip to main content

laterite_auth/
events.rs

1//! The facts the auth service announces on the event bus.
2//!
3//! Each is emitted once it is true: after the row that records it is written.
4//! Listen from a module's `register`:
5//!
6//! ```ignore
7//! registry.listen::<laterite_auth::events::SignedIn>(WelcomeBack);
8//! ```
9
10use laterite_core::Event;
11use serde::{Deserialize, Serialize};
12
13use crate::service::RequestContext;
14
15/// `auth.signed_in`: an operator signed in, by password or by a stay-signed-in
16/// credential.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[non_exhaustive]
19pub struct SignedIn {
20    pub user_id: i64,
21    pub username: String,
22    /// The address the request came from, when known.
23    pub ip_address: Option<String>,
24    pub user_agent: Option<String>,
25}
26
27impl SignedIn {
28    pub fn new(user_id: i64, username: impl Into<String>, ctx: &RequestContext) -> Self {
29        Self {
30            user_id,
31            username: username.into(),
32            ip_address: ctx.ip_address.clone(),
33            user_agent: ctx.user_agent.clone(),
34        }
35    }
36}
37
38impl Event for SignedIn {
39    const NAME: &'static str = "auth.signed_in";
40}
41
42/// `auth.sign_in_failed`: a credential was refused. Counts toward the lockout.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[non_exhaustive]
45pub struct SignInFailed {
46    /// The account the attempt named, `None` when no such account exists.
47    pub user_id: Option<i64>,
48    /// The username as it was typed. Empty for a refused stay-signed-in
49    /// credential, which names no username.
50    pub username: String,
51    pub ip_address: Option<String>,
52    pub user_agent: Option<String>,
53}
54
55impl SignInFailed {
56    pub fn new(user_id: Option<i64>, username: impl Into<String>, ctx: &RequestContext) -> Self {
57        Self {
58            user_id,
59            username: username.into(),
60            ip_address: ctx.ip_address.clone(),
61            user_agent: ctx.user_agent.clone(),
62        }
63    }
64}
65
66impl Event for SignInFailed {
67    const NAME: &'static str = "auth.sign_in_failed";
68}
69
70/// `auth.locked_out`: an attempt was turned away because too many before it
71/// failed.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct LockedOut {
75    pub user_id: Option<i64>,
76    pub username: String,
77    pub ip_address: Option<String>,
78    pub user_agent: Option<String>,
79}
80
81impl LockedOut {
82    pub fn new(user_id: Option<i64>, username: impl Into<String>, ctx: &RequestContext) -> Self {
83        Self {
84            user_id,
85            username: username.into(),
86            ip_address: ctx.ip_address.clone(),
87            user_agent: ctx.user_agent.clone(),
88        }
89    }
90}
91
92impl Event for LockedOut {
93    const NAME: &'static str = "auth.locked_out";
94}
95
96/// `auth.signed_out`: an operator ended their own session.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[non_exhaustive]
99pub struct SignedOut {
100    pub user_id: i64,
101}
102
103impl SignedOut {
104    pub fn new(user_id: i64) -> Self {
105        Self { user_id }
106    }
107}
108
109impl Event for SignedOut {
110    const NAME: &'static str = "auth.signed_out";
111}
112
113/// `auth.password_changed`: an account has a new password, and every other
114/// session it held has ended.
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116#[non_exhaustive]
117pub struct PasswordChanged {
118    pub user_id: i64,
119    /// The operator who changed it: the account itself, or an administrator.
120    /// `None` when a process did, such as the command line.
121    pub changed_by: Option<i64>,
122}
123
124impl PasswordChanged {
125    pub fn new(user_id: i64, changed_by: Option<i64>) -> Self {
126        Self {
127            user_id,
128            changed_by,
129        }
130    }
131
132    /// Whether the account changed its own password.
133    pub fn by_owner(&self) -> bool {
134        self.changed_by == Some(self.user_id)
135    }
136}
137
138impl Event for PasswordChanged {
139    const NAME: &'static str = "auth.password_changed";
140}