Skip to main content

adminx_core/
request.rs

1// adminx-core/src/request.rs
2//
3// A neutral request context. Adapters build this from their framework's request
4// (extracting the query string, path params, authenticated claims, and the CSRF
5// cookie) and hand it to Resource methods.
6
7use std::collections::HashMap;
8
9/// Authenticated principal, decoded from the auth cookie by `auth::build_ctx`.
10#[derive(Debug, Clone, Default)]
11pub struct Claims {
12    pub sub: String,
13    pub email: String,
14    pub role: String,
15    pub roles: Vec<String>,
16    /// MFA step for this session: `"ok"` (fully authenticated) or `"pending"`
17    /// (password verified, awaiting a second factor). Empty on anonymous ctx.
18    pub mfa: String,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct ReqCtx {
23    /// Raw query string (without leading `?`).
24    pub query: String,
25    /// Prefix the adminx app is mounted at (e.g. `/adminx`), used to build links.
26    pub mount: String,
27    pub path_params: HashMap<String, String>,
28    pub headers: HashMap<String, String>,
29    pub claims: Option<Claims>,
30    /// Raw CSRF cookie value, if the request carried one. Compared against the
31    /// hidden form field on POST; see `crate::csrf`.
32    pub csrf: Option<String>,
33}
34
35impl ReqCtx {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn with_query(mut self, query: impl Into<String>) -> Self {
41        self.query = query.into();
42        self
43    }
44
45    pub fn with_mount(mut self, mount: impl Into<String>) -> Self {
46        self.mount = mount.into();
47        self
48    }
49
50    pub fn with_claims(mut self, claims: Claims) -> Self {
51        self.claims = Some(claims);
52        self
53    }
54
55    pub fn with_csrf(mut self, csrf: impl Into<String>) -> Self {
56        self.csrf = Some(csrf.into());
57        self
58    }
59
60    /// All roles for the current principal (`role` plus `roles`), empty if anon.
61    pub fn roles(&self) -> Vec<String> {
62        match &self.claims {
63            Some(c) => {
64                let mut r = c.roles.clone();
65                if !c.role.is_empty() && !r.contains(&c.role) {
66                    r.push(c.role.clone());
67                }
68                r
69            }
70            None => Vec::new(),
71        }
72    }
73}