1use std::sync::OnceLock;
2
3use utoipa::{
4 openapi::security::{ApiKey, ApiKeyValue, HttpAuthScheme, HttpBuilder, SecurityScheme},
5 Modify,
6};
7
8use loco_rs::{app::AppContext, config::JWTLocation as LocoJWTLocation};
10
11#[derive(Default, Debug, Clone, PartialEq, Eq)]
13pub enum JWTLocation {
14 #[default]
15 Bearer,
16 Query(String),
17 Cookie(String),
18}
19
20impl From<&LocoJWTLocation> for JWTLocation {
22 fn from(loco_location: &LocoJWTLocation) -> Self {
23 match loco_location {
24 LocoJWTLocation::Bearer => Self::Bearer,
25 LocoJWTLocation::Query { name } => Self::Query(name.clone()),
26 LocoJWTLocation::Cookie { name } => Self::Cookie(name.clone()),
27 }
28 }
29}
30
31impl From<&loco_rs::config::JWTLocationConfig> for JWTLocation {
33 fn from(cfg: &loco_rs::config::JWTLocationConfig) -> Self {
34 match cfg {
35 loco_rs::config::JWTLocationConfig::Single(loc) => Self::from(loc),
36 loco_rs::config::JWTLocationConfig::Multiple(locs) => {
37 locs.first().map_or(Self::Bearer, Self::from)
38 }
39 }
40 }
41}
42
43impl From<&AppContext> for JWTLocation {
45 fn from(ctx: &AppContext) -> Self {
46 ctx.config
47 .auth
48 .as_ref()
49 .and_then(|auth| auth.jwt.as_ref())
50 .and_then(|jwt| jwt.location.as_ref())
51 .map_or(Self::Bearer, Self::from)
52 }
53}
54
55static JWT_LOCATION: OnceLock<Option<JWTLocation>> = OnceLock::new();
56
57pub fn set_jwt_location(jwt_location: JWTLocation) -> &'static Option<JWTLocation> {
59 JWT_LOCATION.get_or_init(|| Some(jwt_location))
60}
61
62pub fn get_jwt_location() -> Option<&'static JWTLocation> {
63 JWT_LOCATION.get().unwrap_or(&None).as_ref()
64}
65
66pub struct SecurityAddon;
68
69impl Modify for SecurityAddon {
70 fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
71 if let Some(jwt_location) = get_jwt_location() {
72 if let Some(components) = openapi.components.as_mut() {
73 components.add_security_schemes_from_iter([
74 (
75 "jwt_token",
76 match jwt_location {
77 JWTLocation::Bearer => SecurityScheme::Http(
78 HttpBuilder::new()
79 .scheme(HttpAuthScheme::Bearer)
80 .bearer_format("JWT")
81 .build(),
82 ),
83 JWTLocation::Query(name) => {
84 SecurityScheme::ApiKey(ApiKey::Query(ApiKeyValue::new(name)))
85 }
86 JWTLocation::Cookie(name) => {
87 SecurityScheme::ApiKey(ApiKey::Cookie(ApiKeyValue::new(name)))
88 }
89 },
90 ),
91 (
92 "api_key",
93 SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("apikey"))),
94 ),
95 ]);
96 }
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_default_jwt_location() {
107 assert_eq!(JWTLocation::default(), JWTLocation::Bearer);
108 }
109
110 #[test]
111 fn test_set_get_jwt_location() {
112 set_jwt_location(JWTLocation::Bearer);
113 assert_eq!(get_jwt_location(), Some(&JWTLocation::Bearer));
114 }
115
116 #[test]
117 fn test_from_loco_jwt_location() {
118 let loco_bearer = LocoJWTLocation::Bearer;
119 assert_eq!(JWTLocation::from(&loco_bearer), JWTLocation::Bearer);
120
121 let loco_query = LocoJWTLocation::Query {
122 name: "token".to_string(),
123 };
124 assert_eq!(
125 JWTLocation::from(&loco_query),
126 JWTLocation::Query("token".to_string())
127 );
128
129 let loco_cookie = LocoJWTLocation::Cookie {
130 name: "auth".to_string(),
131 };
132 assert_eq!(
133 JWTLocation::from(&loco_cookie),
134 JWTLocation::Cookie("auth".to_string())
135 );
136 }
137}