User Auth Early
parent: tbd commit: 8069fba
Showing 7 changed files with 120 insertions and 36 deletions
src/authentication.rs
@@ -17,9 +17,9 @@ use crate::{ | ||
17 | 17 | }; |
18 | 18 | |
19 | 19 | #[derive(Debug, Serialize, Deserialize)] |
20 | struct UserTokenMetadata { | |
21 | user: User, | |
22 | generated_for: Instance, | |
20 | pub struct UserTokenMetadata { | |
21 | pub user: User, | |
22 | pub generated_for: Instance, | |
23 | 23 | exp: u64, |
24 | 24 | } |
25 | 25 |
src/backend/mod.rs
@@ -16,7 +16,7 @@ use crate::{ | ||
16 | 16 | UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, |
17 | 17 | UserDisplayNameRequest, UserDisplayNameResponse, |
18 | 18 | }, |
19 | UserAuthenticated, | |
19 | ValidatedUserAuthenticated, | |
20 | 20 | }, |
21 | 21 | model::repository::RepositoryView, |
22 | 22 | }; |
@@ -25,30 +25,30 @@ use crate::{ | ||
25 | 25 | pub trait RepositoryBackend: IssuesBackend { |
26 | 26 | async fn create_repository( |
27 | 27 | &mut self, |
28 | request: &UserAuthenticated<CreateRepositoryRequest>, | |
28 | request: &ValidatedUserAuthenticated<CreateRepositoryRequest>, | |
29 | 29 | ) -> Result<CreateRepositoryResponse, Box<dyn Error + Send>>; |
30 | 30 | async fn repository_info( |
31 | 31 | &mut self, |
32 | request: &UserAuthenticated<RepositoryInfoRequest>, | |
32 | request: &ValidatedUserAuthenticated<RepositoryInfoRequest>, | |
33 | 33 | ) -> Result<RepositoryView, Box<dyn Error + Send>>; |
34 | 34 | fn repository_file_inspect( |
35 | 35 | &mut self, |
36 | request: &UserAuthenticated<RepositoryFileInspectRequest>, | |
36 | request: &ValidatedUserAuthenticated<RepositoryFileInspectRequest>, | |
37 | 37 | ) -> Result<RepositoryFileInspectionResponse, Box<dyn Error + Send>>; |
38 | 38 | } |
39 | 39 | |
40 | 40 | pub trait IssuesBackend { |
41 | 41 | fn issues_count( |
42 | 42 | &mut self, |
43 | request: &UserAuthenticated<RepositoryIssuesCountRequest>, | |
43 | request: &ValidatedUserAuthenticated<RepositoryIssuesCountRequest>, | |
44 | 44 | ) -> Result<RepositoryIssuesCountResponse, Box<dyn Error + Send>>; |
45 | 45 | fn issue_labels( |
46 | 46 | &mut self, |
47 | request: &UserAuthenticated<RepositoryIssueLabelsRequest>, | |
47 | request: &ValidatedUserAuthenticated<RepositoryIssueLabelsRequest>, | |
48 | 48 | ) -> Result<RepositoryIssueLabelsResponse, Box<dyn Error + Send>>; |
49 | 49 | fn issues( |
50 | 50 | &mut self, |
51 | request: &UserAuthenticated<RepositoryIssuesRequest>, | |
51 | request: &ValidatedUserAuthenticated<RepositoryIssuesRequest>, | |
52 | 52 | ) -> Result<RepositoryIssuesResponse, Box<dyn Error + Send>>; |
53 | 53 | } |
54 | 54 |
src/connection.rs
@@ -184,10 +184,11 @@ pub async fn connection_worker( | ||
184 | 184 | } else { |
185 | 185 | // This message is targeting this instance |
186 | 186 | match &repository.command { |
187 | RepositoryMessageKind::Request(request) => match request { | |
187 | RepositoryMessageKind::Request(request) => match request.clone() { | |
188 | 188 | RepositoryRequest::CreateRepository(request) => { |
189 | 189 | let mut backend = backend.lock().await; |
190 | let response = backend.create_repository(request).await; | |
190 | let request = request.validate().await.unwrap(); | |
191 | let response = backend.create_repository(&request).await; | |
191 | 192 | |
192 | 193 | let response = match response { |
193 | 194 | Ok(response) => response, |
@@ -217,7 +218,8 @@ pub async fn connection_worker( | ||
217 | 218 | } |
218 | 219 | RepositoryRequest::RepositoryFileInspect(request) => { |
219 | 220 | let mut backend = backend.lock().await; |
220 | let response = backend.repository_file_inspect(request); | |
221 | let request = request.validate().await.unwrap(); | |
222 | let response = backend.repository_file_inspect(&request); | |
221 | 223 | |
222 | 224 | let response = match response { |
223 | 225 | Ok(response) => response, |
@@ -248,7 +250,8 @@ pub async fn connection_worker( | ||
248 | 250 | } |
249 | 251 | RepositoryRequest::RepositoryInfo(request) => { |
250 | 252 | let mut backend = backend.lock().await; |
251 | let response = backend.repository_info(request).await; | |
253 | let request = request.validate().await.unwrap(); | |
254 | let response = backend.repository_info(&request).await; | |
252 | 255 | |
253 | 256 | let response = match response { |
254 | 257 | Ok(response) => response, |
@@ -276,6 +279,8 @@ pub async fn connection_worker( | ||
276 | 279 | continue; |
277 | 280 | } |
278 | 281 | RepositoryRequest::IssuesCount(request) => { |
282 | let request = &request.validate().await.unwrap(); | |
283 | ||
279 | 284 | let mut backend = backend.lock().await; |
280 | 285 | let response = backend.issues_count(request); |
281 | 286 | |
@@ -305,8 +310,10 @@ pub async fn connection_worker( | ||
305 | 310 | continue; |
306 | 311 | } |
307 | 312 | RepositoryRequest::IssueLabels(request) => { |
313 | let request = &request.validate().await.unwrap(); | |
314 | ||
308 | 315 | let mut backend = backend.lock().await; |
309 | let response = backend.issue_labels(request); | |
316 | let response = backend.issue_labels(&request); | |
310 | 317 | |
311 | 318 | let response = match response { |
312 | 319 | Ok(response) => response, |
@@ -333,8 +340,10 @@ pub async fn connection_worker( | ||
333 | 340 | continue; |
334 | 341 | } |
335 | 342 | RepositoryRequest::Issues(request) => { |
343 | let request = request.validate().await.unwrap(); | |
344 | ||
336 | 345 | let mut backend = backend.lock().await; |
337 | let response = backend.issues(request); | |
346 | let response = backend.issues(&request); | |
338 | 347 | |
339 | 348 | let response = match response { |
340 | 349 | Ok(response) => response, |
src/messages/mod.rs
@@ -1,5 +1,6 @@ | ||
1 | 1 | use std::{error::Error, fmt::Debug}; |
2 | 2 | |
3 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | |
3 | 4 | use rsa::{ |
4 | 5 | pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, |
5 | 6 | pss::{Signature, SigningKey, VerifyingKey}, |
@@ -9,7 +10,11 @@ use rsa::{ | ||
9 | 10 | }; |
10 | 11 | use serde::{Deserialize, Serialize}; |
11 | 12 | |
12 | use crate::{handshake::HandshakeMessage, model::instance::Instance}; | |
13 | use crate::{ | |
14 | authentication::UserTokenMetadata, | |
15 | handshake::HandshakeMessage, | |
16 | model::{instance::Instance, user::User}, | |
17 | }; | |
13 | 18 | |
14 | 19 | use self::{authentication::AuthenticationMessage, repository::RepositoryMessage}; |
15 | 20 | |
@@ -110,14 +115,59 @@ impl<T: Serialize> InstanceAuthenticated<T> { | ||
110 | 115 | /// Includes the message, with a digest generated with |
111 | 116 | /// our private key. |
112 | 117 | #[derive(Serialize, Deserialize)] |
113 | pub struct UserAuthenticated<T: Serialize> { | |
118 | pub struct ValidatedUserAuthenticated<T: Serialize> { | |
119 | #[serde(flatten)] | |
120 | message: T, | |
121 | user: User, | |
122 | } | |
123 | ||
124 | impl<T> Clone for ValidatedUserAuthenticated<T> | |
125 | where | |
126 | T: Clone + Serialize, | |
127 | { | |
128 | fn clone(&self) -> Self { | |
129 | Self { | |
130 | message: self.message.clone(), | |
131 | user: self.user.clone(), | |
132 | } | |
133 | } | |
134 | } | |
135 | ||
136 | impl<T> Debug for ValidatedUserAuthenticated<T> | |
137 | where | |
138 | T: Debug + Serialize, | |
139 | { | |
140 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
141 | f.debug_struct("Authenticated") | |
142 | .field("message", &self.message) | |
143 | .field("user", &self.user) | |
144 | .finish() | |
145 | } | |
146 | } | |
147 | ||
148 | impl<T: Serialize> ValidatedUserAuthenticated<T> { | |
149 | pub async fn inner(&self) -> &T { | |
150 | &self.message | |
151 | } | |
152 | ||
153 | pub async fn user(&self) -> &User { | |
154 | &self.user | |
155 | } | |
156 | } | |
157 | ||
158 | /// An unvalidated authenticated message. | |
159 | /// | |
160 | /// Includes the message, with a digest generated with | |
161 | /// our private key. | |
162 | #[derive(Serialize, Deserialize)] | |
163 | pub struct UnvalidatedUserAuthenticated<T: Serialize> { | |
114 | 164 | #[serde(flatten)] |
115 | 165 | message: T, |
116 | 166 | token: String, |
117 | 167 | digest: Vec<u8>, |
118 | 168 | } |
119 | 169 | |
120 | impl<T> Clone for UserAuthenticated<T> | |
170 | impl<T> Clone for UnvalidatedUserAuthenticated<T> | |
121 | 171 | where |
122 | 172 | T: Clone + Serialize, |
123 | 173 | { |
@@ -130,7 +180,7 @@ where | ||
130 | 180 | } |
131 | 181 | } |
132 | 182 | |
133 | impl<T> Debug for UserAuthenticated<T> | |
183 | impl<T> Debug for UnvalidatedUserAuthenticated<T> | |
134 | 184 | where |
135 | 185 | T: Debug + Serialize, |
136 | 186 | { |
@@ -143,7 +193,7 @@ where | ||
143 | 193 | } |
144 | 194 | } |
145 | 195 | |
146 | impl<T: Serialize> UserAuthenticated<T> { | |
196 | impl<T: Serialize> UnvalidatedUserAuthenticated<T> { | |
147 | 197 | pub fn new(message: T, token: String, private_key: String) -> Result<Self, Box<dyn Error>> { |
148 | 198 | let mut rng = rand::thread_rng(); |
149 | 199 | |
@@ -165,8 +215,19 @@ impl<T: Serialize> UserAuthenticated<T> { | ||
165 | 215 | &self.message |
166 | 216 | } |
167 | 217 | |
168 | pub async fn validate(&self, key: String) -> Result<(), Box<dyn Error>> { | |
169 | let public_key = RsaPublicKey::from_pkcs1_pem(&key).unwrap(); | |
218 | pub async fn validate(self) -> Result<ValidatedUserAuthenticated<T>, Box<dyn Error>> { | |
219 | let instance = { | |
220 | let mut validation = Validation::new(Algorithm::RS256); | |
221 | validation.insecure_disable_signature_validation(); | |
222 | ||
223 | let value: TokenData<UserTokenMetadata> = | |
224 | decode(&self.token, &DecodingKey::from_secret(b"test"), &validation).unwrap(); | |
225 | ||
226 | value.claims.generated_for.clone() | |
227 | }; | |
228 | ||
229 | let public_key_raw = public_key(&instance).await?; | |
230 | let public_key = RsaPublicKey::from_pkcs1_pem(&public_key_raw).unwrap(); | |
170 | 231 | |
171 | 232 | let verifying_key: VerifyingKey<Sha256> = VerifyingKey::new(public_key); |
172 | 233 | |
@@ -179,7 +240,21 @@ impl<T: Serialize> UserAuthenticated<T> { | ||
179 | 240 | ) |
180 | 241 | .unwrap(); |
181 | 242 | |
182 | Ok(()) | |
243 | let verification_key = DecodingKey::from_rsa_pem(public_key_raw.as_bytes()).unwrap(); | |
244 | ||
245 | let data: TokenData<UserTokenMetadata> = decode( | |
246 | &self.token, | |
247 | &verification_key, | |
248 | &Validation::new(Algorithm::RS256), | |
249 | ) | |
250 | .unwrap(); | |
251 | ||
252 | assert_eq!(data.claims.generated_for, instance); | |
253 | ||
254 | Ok(ValidatedUserAuthenticated { | |
255 | message: self.message, | |
256 | user: data.claims.user, | |
257 | }) | |
183 | 258 | } |
184 | 259 | } |
185 | 260 |
src/messages/repository.rs
@@ -6,7 +6,7 @@ use crate::model::{ | ||
6 | 6 | user::User, |
7 | 7 | }; |
8 | 8 | |
9 | use super::UserAuthenticated; | |
9 | use super::UnvalidatedUserAuthenticated; | |
10 | 10 | |
11 | 11 | #[derive(Clone, Serialize, Deserialize)] |
12 | 12 | pub struct RepositoryMessage { |
@@ -22,12 +22,12 @@ pub enum RepositoryMessageKind { | ||
22 | 22 | |
23 | 23 | #[derive(Clone, Serialize, Deserialize)] |
24 | 24 | pub enum RepositoryRequest { |
25 | CreateRepository(UserAuthenticated<CreateRepositoryRequest>), | |
26 | RepositoryFileInspect(UserAuthenticated<RepositoryFileInspectRequest>), | |
27 | RepositoryInfo(UserAuthenticated<RepositoryInfoRequest>), | |
28 | IssuesCount(UserAuthenticated<RepositoryIssuesCountRequest>), | |
29 | IssueLabels(UserAuthenticated<RepositoryIssueLabelsRequest>), | |
30 | Issues(UserAuthenticated<RepositoryIssuesRequest>), | |
25 | CreateRepository(UnvalidatedUserAuthenticated<CreateRepositoryRequest>), | |
26 | RepositoryFileInspect(UnvalidatedUserAuthenticated<RepositoryFileInspectRequest>), | |
27 | RepositoryInfo(UnvalidatedUserAuthenticated<RepositoryInfoRequest>), | |
28 | IssuesCount(UnvalidatedUserAuthenticated<RepositoryIssuesCountRequest>), | |
29 | IssueLabels(UnvalidatedUserAuthenticated<RepositoryIssueLabelsRequest>), | |
30 | Issues(UnvalidatedUserAuthenticated<RepositoryIssuesRequest>), | |
31 | 31 | } |
32 | 32 | |
33 | 33 | #[derive(Clone, Serialize, Deserialize)] |
src/model/repository.rs
@@ -5,7 +5,7 @@ use super::{instance::Instance, user::User}; | ||
5 | 5 | #[derive(Hash, Clone, Serialize, Deserialize)] |
6 | 6 | pub struct Repository { |
7 | 7 | /// Name of the repository. |
8 | /// | |
8 | /// | |
9 | 9 | /// This must be treated as an opaque path to a repository. Do not |
10 | 10 | /// modify or parse the path. |
11 | 11 | pub name: String, |
@@ -25,7 +25,7 @@ pub enum RepositoryVisibility { | ||
25 | 25 | #[derive(Clone, Debug, Serialize, Deserialize)] |
26 | 26 | pub struct RepositoryView { |
27 | 27 | /// Name of the repository |
28 | /// | |
28 | /// | |
29 | 29 | /// This is different than the [`Repository`] name, |
30 | 30 | /// which may be a path. |
31 | 31 | pub name: String, |
@@ -134,6 +134,6 @@ impl From<git2::Signature<'_>> for CommitSignature { | ||
134 | 134 | |
135 | 135 | impl ToString for Repository { |
136 | 136 | fn to_string(&self) -> String { |
137 | format!("{}@{}",self.name, self.instance.url) | |
137 | format!("{}@{}", self.name, self.instance.url) | |
138 | 138 | } |
139 | } | |
139 | \ No newline at end of file | |
139 | } |