systemprompt_config/services/
security_config.rs1use systemprompt_models::profile::{SecurityConfig, TrustedIssuer, default_resource_audiences};
15
16use crate::error::{ConfigError, ConfigResult};
17
18#[derive(Debug, Clone)]
19pub struct SecurityChange {
20 pub field: String,
21 pub old_value: String,
22 pub new_value: String,
23 pub message: String,
24}
25
26#[derive(Debug, Clone, Default)]
27pub struct SecurityUpdate {
28 pub jwt_issuer: Option<String>,
29 pub access_token_expiration: Option<i64>,
30 pub refresh_token_expiration: Option<i64>,
31 pub resource_audiences: Vec<String>,
32}
33
34#[derive(Debug, Clone, Copy)]
35pub struct SecurityConfigService;
36
37impl SecurityConfigService {
38 pub fn apply_update(
39 security: &mut SecurityConfig,
40 update: &SecurityUpdate,
41 ) -> ConfigResult<Vec<SecurityChange>> {
42 let mut changes = Vec::new();
43
44 if let Some(ref issuer) = update.jwt_issuer {
45 let old = security.issuer.clone();
46 security.issuer.clone_from(issuer);
47 changes.push(SecurityChange {
48 field: "jwt_issuer".to_owned(),
49 old_value: old,
50 new_value: issuer.clone(),
51 message: format!("Updated JWT issuer to {}", issuer),
52 });
53 }
54
55 if let Some(expiry) = update.access_token_expiration {
56 if expiry <= 0 {
57 return Err(ConfigError::NonPositiveAccessTokenExpiry);
58 }
59 let old = security.access_token_expiration;
60 security.access_token_expiration = expiry;
61 changes.push(SecurityChange {
62 field: "access_token_expiration".to_owned(),
63 old_value: old.to_string(),
64 new_value: expiry.to_string(),
65 message: format!("Updated access token expiry to {} seconds", expiry),
66 });
67 }
68
69 if let Some(expiry) = update.refresh_token_expiration {
70 if expiry <= 0 {
71 return Err(ConfigError::NonPositiveRefreshTokenExpiry);
72 }
73 let old = security.refresh_token_expiration;
74 security.refresh_token_expiration = expiry;
75 changes.push(SecurityChange {
76 field: "refresh_token_expiration".to_owned(),
77 old_value: old.to_string(),
78 new_value: expiry.to_string(),
79 message: format!("Updated refresh token expiry to {} seconds", expiry),
80 });
81 }
82
83 if !update.resource_audiences.is_empty() {
84 changes.push(Self::merge_resource_audiences(
85 security,
86 &update.resource_audiences,
87 ));
88 }
89
90 Ok(changes)
91 }
92
93 fn merge_resource_audiences(
94 security: &mut SecurityConfig,
95 requested: &[String],
96 ) -> SecurityChange {
97 let old = security.allowed_resource_audiences.join(",");
98 let mut merged = default_resource_audiences();
99 for aud in requested {
100 if !merged.contains(aud) {
101 merged.push(aud.clone());
102 }
103 }
104 security.allowed_resource_audiences.clone_from(&merged);
105 SecurityChange {
106 field: "allowed_resource_audiences".to_owned(),
107 old_value: old,
108 new_value: merged.join(","),
109 message: "Updated allowed resource audiences".to_owned(),
110 }
111 }
112
113 pub fn upsert_trusted_issuer(
114 security: &mut SecurityConfig,
115 entry: TrustedIssuer,
116 ) -> SecurityChange {
117 security
118 .trusted_issuers
119 .retain(|t| t.issuer != entry.issuer);
120 let change = SecurityChange {
121 field: "trusted_issuers".to_owned(),
122 old_value: String::new(),
123 new_value: entry.issuer.clone(),
124 message: format!("Added trusted issuer {}", entry.issuer),
125 };
126 security.trusted_issuers.push(entry);
127 change
128 }
129
130 pub fn remove_trusted_issuer(
131 security: &mut SecurityConfig,
132 issuer: &str,
133 ) -> ConfigResult<SecurityChange> {
134 let before = security.trusted_issuers.len();
135 security.trusted_issuers.retain(|t| t.issuer != issuer);
136 if security.trusted_issuers.len() == before {
137 return Err(ConfigError::TrustedIssuerNotFound {
138 issuer: issuer.to_owned(),
139 });
140 }
141 Ok(SecurityChange {
142 field: "trusted_issuers".to_owned(),
143 old_value: issuer.to_owned(),
144 new_value: String::new(),
145 message: format!("Removed trusted issuer {}", issuer),
146 })
147 }
148}