Skip to main content

backbone_auth/
middleware.rs

1//! Authentication middleware
2
3use anyhow::Result;
4use async_trait::async_trait;
5
6/// Authentication middleware trait
7#[async_trait]
8pub trait AuthMiddleware {
9    async fn authenticate(&self, token: &str) -> Result<AuthContext>;
10}
11
12/// Trait for extracting authentication information from requests
13#[async_trait]
14pub trait AuthExtractor {
15    async fn extract_token(&self) -> Result<Option<String>>;
16    async fn extract_user_context(&self) -> Result<Option<AuthContext>>;
17}
18
19/// Authentication context
20#[derive(Debug, Clone)]
21pub struct AuthContext {
22    pub user_id: String,
23    pub roles: Vec<String>,
24    pub permissions: Vec<String>,
25}
26
27impl AuthContext {
28    pub fn new(user_id: String) -> Self {
29        Self {
30            user_id,
31            roles: Vec::new(),
32            permissions: Vec::new(),
33        }
34    }
35
36    /// Whether `permission` (a canonical `<resource>:<action>`) is among what the caller holds.
37    ///
38    /// Reads `self.permissions` and nothing else: with no grant resolver installed that is whatever
39    /// the host's authentication put there, with one installed it is what the resolver loaded. An
40    /// empty list holds nothing. Wildcards (`ticket:*`, `*:read`, `*:*`) and the legacy spellings
41    /// are matched as [`crate::grants`] describes.
42    pub fn has_permission(&self, permission: &str) -> bool {
43        crate::grants::check_held(self.permissions.iter(), permission, &[])
44    }
45
46    /// Refuse unless the caller holds `permission`.
47    ///
48    /// The generated routes, transitions and verbs all call this. The error carries the permission,
49    /// and answers `403` with the violation `permission_required` and `params.permission`.
50    pub fn require(&self, permission: &str) -> Result<(), crate::grants::PermissionRequired> {
51        self.require_with(permission, &[])
52    }
53
54    /// [`require`](Self::require), also accepting any of `alternates` (legacy spellings a route
55    /// still honours for one release, such as the module-wide `<module>:<action>`). The refusal
56    /// always names `permission`.
57    pub fn require_with(
58        &self,
59        permission: &str,
60        alternates: &[&str],
61    ) -> Result<(), crate::grants::PermissionRequired> {
62        if crate::grants::check_held(self.permissions.iter(), permission, alternates) {
63            Ok(())
64        } else {
65            Err(crate::grants::PermissionRequired { permission: permission.to_string() })
66        }
67    }
68}