systemprompt_api/services/middleware/
authz.rs1use axum::extract::Request;
21use axum::middleware::Next;
22use axum::response::{IntoResponse, Response};
23use systemprompt_models::RequestContext;
24use systemprompt_models::api::ApiError;
25use systemprompt_models::auth::UserType;
26
27#[derive(Clone, Copy, Debug)]
28pub struct AuthzPolicy {
29 allowed: &'static [UserType],
30}
31
32impl AuthzPolicy {
33 #[must_use]
34 pub const fn public() -> Self {
35 Self {
36 allowed: &[
37 UserType::Anon,
38 UserType::User,
39 UserType::Admin,
40 UserType::A2a,
41 UserType::Mcp,
42 UserType::Service,
43 ],
44 }
45 }
46
47 #[must_use]
48 pub const fn authenticated() -> Self {
49 Self {
50 allowed: &[
51 UserType::User,
52 UserType::Admin,
53 UserType::A2a,
54 UserType::Mcp,
55 UserType::Service,
56 ],
57 }
58 }
59
60 #[must_use]
61 pub const fn user() -> Self {
62 Self {
63 allowed: &[UserType::User, UserType::Admin],
64 }
65 }
66
67 #[must_use]
68 pub const fn admin() -> Self {
69 Self {
70 allowed: &[UserType::Admin],
71 }
72 }
73
74 #[must_use]
75 pub const fn restricted_to(allowed: &'static [UserType]) -> Self {
76 Self { allowed }
77 }
78
79 fn permits(self, user_type: UserType) -> bool {
80 self.allowed.contains(&user_type)
81 }
82}
83
84pub async fn authz_gate(policy: AuthzPolicy, request: Request, next: Next) -> Response {
85 let user_type = request
89 .extensions()
90 .get::<RequestContext>()
91 .map_or(UserType::Anon, RequestContext::user_type);
92
93 if policy.permits(user_type) {
94 next.run(request).await
95 } else {
96 ApiError::forbidden(format!(
97 "caller type '{}' is not authorized for this route",
98 user_type.as_str()
99 ))
100 .into_response()
101 }
102}