Skip to content

Commit 92687cc

Browse files
committed
Fmt
1 parent dcbd4d7 commit 92687cc

File tree

20 files changed

+1073
-101
lines changed

20 files changed

+1073
-101
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [
44
"parse-rfd",
55
"rfd-api",
66
"rfd-cli",
7-
"rfd-data",
7+
"rfd-data", "rfd-github",
88
"rfd-model",
99
"rfd-processor",
1010
"rfd-redirect",

rfd-api/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ hyper-rustls = { workspace = true }
2525
jsonwebtoken = { workspace = true }
2626
meilisearch-sdk = { workspace = true }
2727
oauth2 = { workspace = true }
28+
octorust = { workspace = true }
2829
partial-struct = { workspace = true }
2930
rand = { workspace = true, features = ["std"] }
3031
rand_core = { workspace = true, features = ["std"] }
3132
regex = { workspace = true }
3233
reqwest = { workspace = true }
34+
reqwest-middleware = { workspace = true }
35+
reqwest-retry = { workspace = true }
36+
reqwest-tracing = { workspace = true }
3337
ring = { workspace = true }
3438
rfd-data = { path = "../rfd-data" }
3539
rfd-model = { path = "../rfd-model" }

rfd-api/src/config.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub struct AppConfig {
3333
pub spec: Option<SpecConfig>,
3434
pub authn: AuthnProviders,
3535
pub search: SearchConfig,
36+
pub services: ServicesConfig,
3637
}
3738

3839
#[derive(Debug)]
@@ -144,6 +145,24 @@ pub struct SearchConfig {
144145
pub index: String,
145146
}
146147

148+
#[derive(Debug, Deserialize)]
149+
pub struct ServicesConfig {
150+
pub github: GitHubAuthConfig,
151+
}
152+
153+
#[derive(Debug, Deserialize)]
154+
#[serde(untagged)]
155+
pub enum GitHubAuthConfig {
156+
Installation {
157+
app_id: i64,
158+
installation_id: i64,
159+
private_key: String,
160+
},
161+
User {
162+
token: String,
163+
},
164+
}
165+
147166
impl AppConfig {
148167
pub fn new(config_sources: Option<Vec<String>>) -> Result<Self, ConfigError> {
149168
let mut config = Config::builder()

rfd-api/src/context.rs

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ use hyper::{client::HttpConnector, Body, Client};
99
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
1010
use jsonwebtoken::jwk::JwkSet;
1111
use oauth2::CsrfToken;
12+
use octorust::{
13+
auth::{Credentials, InstallationTokenGenerator, JWTCredentials},
14+
http_cache::NoCache,
15+
Client as GitHubClient,
16+
};
1217
use partial_struct::partial;
1318
use rfd_model::{
1419
schema_ext::{ContentFormat, LoginAttemptState, Visibility},
@@ -23,8 +28,12 @@ use rfd_model::{
2328
AccessGroup, AccessToken, ApiUser, ApiUserProvider, InvalidValueError, Job, LinkRequest,
2429
LoginAttempt, Mapper, NewAccessGroup, NewAccessToken, NewApiKey, NewApiUser,
2530
NewApiUserProvider, NewJob, NewLinkRequest, NewLoginAttempt, NewMapper, NewOAuthClient,
26-
NewOAuthClientRedirectUri, NewOAuthClientSecret, OAuthClient, OAuthClientRedirectUri,
27-
OAuthClientSecret, Rfd,
31+
NewOAuthClientRedirectUri, NewOAuthClientSecret, NewRfdRevision, OAuthClient,
32+
OAuthClientRedirectUri, OAuthClientSecret, Rfd, RfdRevision,
33+
};
34+
use rsa::{
35+
pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey},
36+
RsaPrivateKey,
2837
};
2938
use schemars::JsonSchema;
3039
use serde::{Deserialize, Serialize};
@@ -45,7 +54,7 @@ use crate::{
4554
key::{RawApiKey, SignedApiKey},
4655
AuthError, AuthToken, Signer,
4756
},
48-
config::{AsymmetricKey, JwtConfig, SearchConfig},
57+
config::{AsymmetricKey, GitHubAuthConfig, JwtConfig, SearchConfig, ServicesConfig},
4958
endpoints::login::{
5059
oauth::{
5160
ClientType, OAuthProvider, OAuthProviderError, OAuthProviderFn, OAuthProviderName,
@@ -118,6 +127,7 @@ pub struct ApiContext {
118127
pub secrets: SecretContext,
119128
pub oauth_providers: HashMap<OAuthProviderName, Box<dyn OAuthProviderFn>>,
120129
pub search: SearchContext,
130+
pub github: GitHubClient,
121131
}
122132

123133
pub struct JwtContext {
@@ -218,13 +228,28 @@ impl ApiContext {
218228
jwt: JwtConfig,
219229
keys: Vec<AsymmetricKey>,
220230
search: SearchConfig,
231+
services: ServicesConfig,
221232
) -> Result<Self, AppError> {
222233
let mut jwt_signers = vec![];
223234

224235
for key in &keys {
225236
jwt_signers.push(JwtSigner::new(&key).await.unwrap())
226237
}
227238

239+
let http = reqwest::Client::builder()
240+
.build()
241+
.map_err(AppError::ClientConstruction)?;
242+
let retry_policy =
243+
reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
244+
let client = reqwest_middleware::ClientBuilder::new(http)
245+
// Trace HTTP requests. See the tracing crate to make use of these traces.
246+
.with(reqwest_tracing::TracingMiddleware::default())
247+
// Retry failed requests.
248+
.with(reqwest_retry::RetryTransientMiddleware::new_with_policy(
249+
retry_policy,
250+
))
251+
.build();
252+
228253
Ok(Self {
229254
https_client: hyper::Client::builder().build(
230255
HttpsConnectorBuilder::new()
@@ -269,6 +294,33 @@ impl ApiContext {
269294
search: SearchContext {
270295
client: SearchClient::new(search.host, search.index, search.key),
271296
},
297+
github: match services.github {
298+
GitHubAuthConfig::Installation {
299+
app_id,
300+
installation_id,
301+
private_key,
302+
} => GitHubClient::custom(
303+
"rfd-api",
304+
Credentials::InstallationToken(InstallationTokenGenerator::new(
305+
installation_id,
306+
JWTCredentials::new(
307+
app_id,
308+
RsaPrivateKey::from_pkcs1_pem(&private_key)?
309+
.to_pkcs1_der()?
310+
.to_bytes()
311+
.to_vec(),
312+
)?,
313+
)),
314+
client,
315+
Box::new(NoCache),
316+
),
317+
GitHubAuthConfig::User { token } => GitHubClient::custom(
318+
"rfd-api",
319+
Credentials::Token(token.to_string()),
320+
client,
321+
Box::new(NoCache),
322+
),
323+
},
272324
})
273325
}
274326

@@ -668,6 +720,67 @@ impl ApiContext {
668720
}
669721
}
670722

723+
#[instrument(skip(self, caller))]
724+
pub async fn get_rfd_revision(
725+
&self,
726+
caller: &ApiCaller,
727+
rfd_number: i32,
728+
sha: Option<String>,
729+
) -> ResourceResult<RfdRevision, StoreError> {
730+
if caller.any(&[
731+
&ApiPermission::GetRfd(rfd_number),
732+
&ApiPermission::GetRfdsAll,
733+
]) {
734+
let rfds = RfdStore::list(
735+
&*self.storage,
736+
RfdFilter::default().rfd_number(Some(vec![rfd_number])),
737+
&ListPagination::default().limit(1),
738+
)
739+
.await
740+
.to_resource_result()?;
741+
if let Some(rfd) = rfds.into_iter().nth(0) {
742+
let latest_revision = RfdRevisionStore::list(
743+
&*self.storage,
744+
RfdRevisionFilter::default()
745+
.rfd(Some(vec![rfd.id]))
746+
.sha(sha.map(|sha| vec![sha])),
747+
&ListPagination::default().limit(1),
748+
)
749+
.await
750+
.to_resource_result()?;
751+
752+
match latest_revision.into_iter().nth(0) {
753+
Some(revision) => Ok(revision),
754+
None => Err(ResourceError::DoesNotExist),
755+
}
756+
} else {
757+
Err(ResourceError::DoesNotExist)
758+
}
759+
} else {
760+
resource_restricted()
761+
}
762+
}
763+
764+
#[instrument(skip(self, caller))]
765+
pub async fn update_rfd_content<'a>(
766+
&self,
767+
caller: &ApiCaller,
768+
rfd_number: i32,
769+
revision: NewRfdRevision,
770+
) -> ResourceResult<RfdRevision, StoreError> {
771+
if caller.any(&[
772+
&ApiPermission::UpdateRfd(rfd_number),
773+
&ApiPermission::UpdateRfdsAll,
774+
]) {
775+
let revision = RfdRevisionStore::upsert(&*self.storage, revision)
776+
.await
777+
.to_resource_result()?;
778+
Ok(revision)
779+
} else {
780+
resource_restricted()
781+
}
782+
}
783+
671784
#[instrument(skip(self, caller))]
672785
pub async fn update_rfd_visibility(
673786
&self,
@@ -1885,7 +1998,7 @@ pub(crate) mod test_mocks {
18851998
use w_api_permissions::Caller;
18861999

18872000
use crate::{
1888-
config::{JwtConfig, SearchConfig},
2001+
config::{GitHubAuthConfig, JwtConfig, SearchConfig, ServicesConfig},
18892002
endpoints::login::oauth::{google::GoogleOAuthProvider, OAuthProviderName},
18902003
permissions::ApiPermission,
18912004
util::tests::mock_key,
@@ -1904,6 +2017,11 @@ pub(crate) mod test_mocks {
19042017
mock_key(),
19052018
],
19062019
SearchConfig::default(),
2020+
ServicesConfig {
2021+
github: GitHubAuthConfig::User {
2022+
token: String::default(),
2023+
},
2024+
},
19072025
)
19082026
.await
19092027
.unwrap();

0 commit comments

Comments
 (0)