better_auth_api/plugins/open_api/
mod.rs1use async_trait::async_trait;
4use better_auth_core::{
5 AuthContext, AuthError, AuthPlugin, AuthRequest, AuthResponse, AuthResult, AuthRoute,
6 AuthSchema, HttpMethod, OpenApiSpec, openapi::OpenApiRegistry,
7};
8
9#[derive(better_auth_core::PluginConfig)]
10#[plugin(name = "OpenApiPlugin")]
11pub struct OpenApiConfig {
12 #[config(default = "/reference".to_owned())]
14 pub path: String,
15 #[config(default = false)]
17 pub disable_default_reference: bool,
18 #[config(default = None)]
20 pub theme: Option<String>,
21 #[config(default = None)]
23 pub nonce: Option<String>,
24}
25
26pub struct OpenApiPlugin {
27 config: OpenApiConfig,
28}
29
30#[async_trait]
31impl<S: AuthSchema> AuthPlugin<S> for OpenApiPlugin {
32 fn name(&self) -> &'static str {
33 "open-api"
34 }
35
36 fn routes(&self) -> Vec<AuthRoute> {
37 vec![
38 AuthRoute::get("/open-api/generate-schema", "generateOpenAPISchema"),
39 AuthRoute::get(&self.config.path, "openAPIReference"),
40 ]
41 }
42
43 async fn on_request(
44 &self,
45 request: &AuthRequest,
46 context: &AuthContext<S>,
47 ) -> AuthResult<Option<AuthResponse>> {
48 if request.method() != &HttpMethod::Get {
49 return Ok(None);
50 }
51 if request.path() == "/open-api/generate-schema" {
52 let mut response =
53 AuthResponse::new(200).with_header("content-type", "application/json");
54 response.body = better_auth_core::ResponseBody::Bytes(
55 better_auth_core::utils::json::stringify(&schema(context)?.to_value()?)?
56 .into_bytes(),
57 );
58 return Ok(Some(response));
59 }
60 if request.path() != self.config.path {
61 return Ok(None);
62 }
63 if self.config.disable_default_reference {
64 return Err(AuthResponse::new(404)
65 .with_header("content-type", "application/json")
66 .into());
67 }
68 let html = reference_html(&schema(context)?, &self.config)?;
69 let mut response = AuthResponse::html(200, html);
70 let _ = response.headers.insert("content-type", "text/html");
71 Ok(Some(response))
72 }
73}
74
75fn schema(context: &AuthContext<impl AuthSchema>) -> AuthResult<OpenApiSpec> {
76 context
77 .extensions
78 .get::<OpenApiRegistry>()
79 .map(|registry| registry.generate(context.base_url()))
80 .ok_or_else(|| AuthError::config("OpenAPI registry is not initialized"))
81}
82
83fn reference_html(spec: &OpenApiSpec, config: &OpenApiConfig) -> AuthResult<String> {
84 let nonce = config
85 .nonce
86 .as_deref()
87 .filter(|value| !value.is_empty())
88 .map(|value| format!("nonce=\"{value}\""))
89 .unwrap_or_default();
90 let theme = config
91 .theme
92 .as_deref()
93 .filter(|value| !value.is_empty())
94 .unwrap_or("default");
95 Ok(format!(
97 include_str!("reference.html"),
98 schema = better_auth_core::utils::json::stringify(&spec.to_value()?)?,
99 favicon_indent = "\t \t",
101 theme_indent = "\t \t",
102 theme = theme,
103 nonce = nonce,
104 ))
105}