Skip to main content

systemprompt_extension/
frame_options.rs

1//! Per-route framing policy override for extension routers.
2//!
3//! The host's global security-headers middleware sets `X-Frame-Options`
4//! sitewide. An extension that must allow its pages to be framed (embed
5//! widgets, chrome-free tool pages) declares a [`FrameOptions`] on its
6//! router; [`stamp_frame_options`] records the choice as a
7//! [`FrameOptionsOverride`] response extension, which the host middleware
8//! honours instead of the profile default. Setting the raw header without
9//! the marker has no effect — the global middleware overwrites it.
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14use axum::extract::Request;
15use axum::middleware::Next;
16use axum::response::Response;
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum FrameOptions {
21    #[serde(rename = "DENY")]
22    Deny,
23    #[serde(rename = "SAMEORIGIN")]
24    SameOrigin,
25    #[serde(rename = "ALLOWALL")]
26    AllowAll,
27}
28
29impl FrameOptions {
30    /// `X-Frame-Options` value for this policy. `None` for [`Self::AllowAll`]:
31    /// the header has no allow-all value, so absence is the mechanism.
32    #[must_use]
33    pub const fn header_value(self) -> Option<&'static str> {
34        match self {
35            Self::Deny => Some("DENY"),
36            Self::SameOrigin => Some("SAMEORIGIN"),
37            Self::AllowAll => None,
38        }
39    }
40
41    #[must_use]
42    pub const fn frame_ancestors(self) -> &'static str {
43        match self {
44            Self::Deny => "'none'",
45            Self::SameOrigin => "'self'",
46            Self::AllowAll => "*",
47        }
48    }
49}
50
51/// Response-extension marker read by the host's security-headers middleware.
52#[derive(Debug, Clone, Copy)]
53pub struct FrameOptionsOverride(pub FrameOptions);
54
55pub async fn stamp_frame_options(
56    frame_options: FrameOptions,
57    request: Request,
58    next: Next,
59) -> Response {
60    let mut response = next.run(request).await;
61    response
62        .extensions_mut()
63        .insert(FrameOptionsOverride(frame_options));
64    response
65}