JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

User Auth Early

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨8069fba

Showing ⁨⁨7⁩ changed files⁩ with ⁨⁨120⁩ insertions⁩ and ⁨⁨36⁩ deletions⁩

src/authentication.rs

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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 }

src/model/user.rs

View file
@@ -12,4 +12,4 @@ impl ToString for User {
12 12 fn to_string(&self) -> String {
13 13 format!("{}:{}", self.username, self.instance.url)
14 14 }
15 }
15 \ No newline at end of file
15 }