44// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
55// Please see LICENSE files in the repository root for full details.
66
7+ use anyhow:: bail;
8+ use camino:: Utf8PathBuf ;
79use rand:: {
810 Rng ,
911 distributions:: { Alphanumeric , DistString } ,
@@ -48,6 +50,54 @@ pub enum HomeserverKind {
4850 SynapseModern ,
4951}
5052
53+ /// Shared secret between MAS and the homeserver.
54+ ///
55+ /// It either holds the secret value directly or references a file where the
56+ /// secret is stored.
57+ #[ derive( Clone , Debug ) ]
58+ pub enum Secret {
59+ File ( Utf8PathBuf ) ,
60+ Value ( String ) ,
61+ }
62+
63+ /// Secret fields as serialized in JSON.
64+ #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
65+ struct SecretRaw {
66+ #[ schemars( with = "Option<String>" ) ]
67+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
68+ secret_file : Option < Utf8PathBuf > ,
69+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
70+ secret : Option < String > ,
71+ }
72+
73+ impl TryFrom < SecretRaw > for Secret {
74+ type Error = anyhow:: Error ;
75+
76+ fn try_from ( value : SecretRaw ) -> Result < Self , Self :: Error > {
77+ match ( value. secret , value. secret_file ) {
78+ ( None , None ) => bail ! ( "Missing `secret` or `secret_file`" ) ,
79+ ( None , Some ( path) ) => Ok ( Secret :: File ( path) ) ,
80+ ( Some ( secret) , None ) => Ok ( Secret :: Value ( secret) ) ,
81+ ( Some ( _) , Some ( _) ) => bail ! ( "Cannot specify both `secret` and `secret_file`" ) ,
82+ }
83+ }
84+ }
85+
86+ impl From < Secret > for SecretRaw {
87+ fn from ( value : Secret ) -> Self {
88+ match value {
89+ Secret :: File ( path) => SecretRaw {
90+ secret_file : Some ( path) ,
91+ secret : None ,
92+ } ,
93+ Secret :: Value ( secret) => SecretRaw {
94+ secret_file : None ,
95+ secret : Some ( secret) ,
96+ } ,
97+ }
98+ }
99+ }
100+
51101/// Configuration related to the Matrix homeserver
52102#[ serde_as]
53103#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
@@ -61,7 +111,10 @@ pub struct MatrixConfig {
61111 pub homeserver : String ,
62112
63113 /// Shared secret to use for calls to the admin API
64- pub secret : String ,
114+ #[ schemars( with = "SecretRaw" ) ]
115+ #[ serde_as( as = "serde_with::TryFromInto<SecretRaw>" ) ]
116+ #[ serde( flatten) ]
117+ pub secret : Secret ,
65118
66119 /// The base URL of the homeserver's client API
67120 #[ serde( default = "default_endpoint" ) ]
@@ -73,14 +126,28 @@ impl ConfigurationSection for MatrixConfig {
73126}
74127
75128impl MatrixConfig {
129+ /// Returns the shared secret.
130+ ///
131+ /// If `secret_file` was given, the secret is read from that file.
132+ ///
133+ /// # Errors
134+ ///
135+ /// Returns an error when the shared secret could not be read from file.
136+ pub async fn secret ( & self ) -> anyhow:: Result < String > {
137+ Ok ( match & self . secret {
138+ Secret :: File ( path) => tokio:: fs:: read_to_string ( path) . await ?,
139+ Secret :: Value ( secret) => secret. clone ( ) ,
140+ } )
141+ }
142+
76143 pub ( crate ) fn generate < R > ( mut rng : R ) -> Self
77144 where
78145 R : Rng + Send ,
79146 {
80147 Self {
81148 kind : HomeserverKind :: default ( ) ,
82149 homeserver : default_homeserver ( ) ,
83- secret : Alphanumeric . sample_string ( & mut rng, 32 ) ,
150+ secret : Secret :: Value ( Alphanumeric . sample_string ( & mut rng, 32 ) ) ,
84151 endpoint : default_endpoint ( ) ,
85152 }
86153 }
@@ -89,7 +156,7 @@ impl MatrixConfig {
89156 Self {
90157 kind : HomeserverKind :: default ( ) ,
91158 homeserver : default_homeserver ( ) ,
92- secret : "test" . to_owned ( ) ,
159+ secret : Secret :: Value ( "test" . to_owned ( ) ) ,
93160 endpoint : default_endpoint ( ) ,
94161 }
95162 }
@@ -101,29 +168,69 @@ mod tests {
101168 Figment , Jail ,
102169 providers:: { Format , Yaml } ,
103170 } ;
171+ use tokio:: runtime:: Handle ;
172+ use tokio:: task;
104173
105174 use super :: * ;
106175
107- #[ test]
108- fn load_config ( ) {
109- Jail :: expect_with ( |jail| {
110- jail. create_file (
111- "config.yaml" ,
112- r"
113- matrix:
114- homeserver: matrix.org
115- secret: test
116- " ,
117- ) ?;
118-
119- let config = Figment :: new ( )
120- . merge ( Yaml :: file ( "config.yaml" ) )
121- . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
122-
123- assert_eq ! ( & config. homeserver, "matrix.org" ) ;
124- assert_eq ! ( & config. secret, "test" ) ;
125-
126- Ok ( ( ) )
127- } ) ;
176+ #[ tokio:: test]
177+ async fn load_config ( ) {
178+ task:: spawn_blocking ( || {
179+ Jail :: expect_with ( |jail| {
180+ jail. create_file (
181+ "config.yaml" ,
182+ r"
183+ matrix:
184+ homeserver: matrix.org
185+ secret_file: secret
186+ " ,
187+ ) ?;
188+ jail. create_file ( "secret" , r"m472!x53c237" ) ?;
189+
190+ let config = Figment :: new ( )
191+ . merge ( Yaml :: file ( "config.yaml" ) )
192+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
193+
194+ Handle :: current ( ) . block_on ( async move {
195+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
196+ assert ! ( matches!( config. secret, Secret :: File ( ref p) if p == "secret" ) ) ;
197+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
198+ } ) ;
199+
200+ Ok ( ( ) )
201+ } ) ;
202+ } )
203+ . await
204+ . unwrap ( ) ;
205+ }
206+
207+ #[ tokio:: test]
208+ async fn load_config_inline_secrets ( ) {
209+ task:: spawn_blocking ( || {
210+ Jail :: expect_with ( |jail| {
211+ jail. create_file (
212+ "config.yaml" ,
213+ r"
214+ matrix:
215+ homeserver: matrix.org
216+ secret: m472!x53c237
217+ " ,
218+ ) ?;
219+
220+ let config = Figment :: new ( )
221+ . merge ( Yaml :: file ( "config.yaml" ) )
222+ . extract_inner :: < MatrixConfig > ( "matrix" ) ?;
223+
224+ Handle :: current ( ) . block_on ( async move {
225+ assert_eq ! ( & config. homeserver, "matrix.org" ) ;
226+ assert ! ( matches!( config. secret, Secret :: Value ( ref v) if v == "m472!x53c237" ) ) ;
227+ assert_eq ! ( config. secret( ) . await . unwrap( ) , "m472!x53c237" ) ;
228+ } ) ;
229+
230+ Ok ( ( ) )
231+ } ) ;
232+ } )
233+ . await
234+ . unwrap ( ) ;
128235 }
129236}
0 commit comments