Skip to main content

rustlavel_audit/
plugin.rs

1//! Attaching the package to an application, and reaching it from a handler.
2//!
3//! ```ignore
4//! App::new().plugin(Audit::new(db.clone()))
5//! ```
6//!
7//! The migrations are not registered from here: they belong in the registry
8//! the CLI generates, next to the application's own. Add them with
9//! `registry.extend(rustlavel_audit::migrations());`
10
11use crate::entry::{Builder, Entry};
12use crate::store::Trail;
13use rustlavel_auth::guard::AuthExt;
14use rustlavel_db::Database;
15use rustlavel_http::Request;
16use rustlavel_http::plugin::{Plugin, Setup};
17
18/// Registers the [`Trail`] into application state.
19pub struct Audit {
20    trail: Trail,
21}
22
23impl Audit {
24    pub fn new(db: Database) -> Audit {
25        Audit { trail: Trail::new(db) }
26    }
27
28    pub fn with_trail(trail: Trail) -> Audit {
29        Audit { trail }
30    }
31
32    pub fn trail(&self) -> &Trail {
33        &self.trail
34    }
35}
36
37impl Plugin for Audit {
38    fn name(&self) -> &'static str {
39        "audit"
40    }
41
42    fn register(self: Box<Self>, setup: &mut Setup<'_>) {
43        setup.state(self.trail.clone());
44    }
45}
46
47/// `req.audit(...)` — an entry with the request's context already on it.
48pub trait AuditExt {
49    /// The trail, or `None` when the plugin is not registered.
50    fn trail(&self) -> Option<&Trail>;
51
52    /// Begin an entry, with the actor's id, the address and the user agent
53    /// filled in from this request.
54    ///
55    /// The actor's *name* is not, and cannot be: the framework's `Identity`
56    /// holds an id and nothing else, on purpose. The caller adds it with
57    /// [`Builder::by`], from whatever it is already holding.
58    ///
59    /// Returns `None` rather than a silent no-op builder when there is no
60    /// trail: a caller that meant to record something should find out that it
61    /// did not, and `let Some(audit) = req.audit(..)` is where they find out.
62    fn audit(&self, event: &str) -> Option<Builder>;
63}
64
65impl AuditExt for Request {
66    fn trail(&self) -> Option<&Trail> {
67        self.state::<Trail>()
68    }
69
70    fn audit(&self, event: &str) -> Option<Builder> {
71        let trail = self.state::<Trail>()?;
72
73        let mut entry = Entry::new(event);
74        // Read off the request rather than passed in. An entry that says
75        // "someone updated the settings" answers nothing, and a caller in a
76        // hurry is exactly who writes that entry.
77        if let Some(identity) = self.identity() {
78            entry.user_id = identity.id_as::<i64>();
79        }
80        entry.ip_address = self.ip();
81        entry.user_agent = self.header("user-agent").map(|agent| agent.to_string());
82
83        Some(Builder { trail: trail.clone(), entry })
84    }
85}