Skip to content

Commit

Permalink
API: Add API Key for matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthi-chaud committed Apr 8, 2024
1 parent 8b38e4f commit 2db8c74
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 4 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RABBIT_PASS=

# Fill these values with random, secure strings
SCANNER_API_KEY=
MATCHER_API_KEY=
# Where can Blee find the video files
DATA_DIR=

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jobs:
ROCKET_DATABASES: '{sea_orm={url="postgresql://test:test@localhost:5432/test"}}'
CONFIG_DIR: "./data"
SCANNER_API_KEY: "API_KEY"
MATCHER_API_KEY: "API_KEY"
RABBIT_USER: test
RABBIT_PASS: test
RABBIT_HOST: localhost
Expand Down
1 change: 1 addition & 0 deletions api/api/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ use serde::{Deserialize, Serialize};
pub struct Config {
pub data_folder: String,
pub scanner_api_key: String,
pub matcher_api_key: String,
}
3 changes: 2 additions & 1 deletion api/api/src/controllers/external_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::database::Database;
use crate::dto::external_id::{ExternalIdFilter, ExternalIdResponse, NewExternalId};
use crate::dto::page::{Page, Pagination};
use crate::error_handling::{ApiError, ApiPageResult, ApiRawResult};
use crate::guards::MatcherAuthGuard;
use crate::services;
use rocket::response::status;
use rocket::serde::uuid::Uuid;
Expand All @@ -19,7 +20,7 @@ pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec<rocket::Route>, O
async fn new_external_id(
db: Database<'_>,
data: Json<NewExternalId>,
// _scanner: ScannerAuthGuard,
_matcher: MatcherAuthGuard,
) -> ApiRawResult<status::Created<Json<ExternalIdResponse>>> {
if data.package_id.is_some() && data.artist_id.is_some()
|| (data.package_id.is_none() && data.artist_id.is_none())
Expand Down
2 changes: 1 addition & 1 deletion api/api/src/dto/external_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ pub struct NewExternalId {
pub struct ExternalIdFilter {
pub artist: Option<Uuid>,
pub package: Option<Uuid>,
}
}
72 changes: 72 additions & 0 deletions api/api/src/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use crate::config::Config;
/// The Request Guard for API Key, used by the Scanner to authenticate itself
pub struct ScannerAuthGuard;

/// The Request Guard for API Key, used by the Matcher to authenticate itself
pub struct MatcherAuthGuard;

#[derive(Debug)]
pub enum ApiKeyError {
Missing,
Expand All @@ -38,6 +41,26 @@ impl<'r> FromRequest<'r> for ScannerAuthGuard {
}
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for MatcherAuthGuard {
type Error = ApiKeyError;

async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let matcher_api_key = req
.rocket()
.state::<Config>()
.unwrap()
.matcher_api_key
.clone();

match req.headers().get_one("x-api-key") {
None => Outcome::Error((Status::Unauthorized, ApiKeyError::Missing)),
Some(key) if key == matcher_api_key => Outcome::Success(MatcherAuthGuard),
Some(_) => Outcome::Error((Status::BadRequest, ApiKeyError::Invalid)),
}
}
}

impl<'r> OpenApiFromRequest<'r> for ScannerAuthGuard {
fn from_request_input(
_gen: &mut OpenApiGenerator,
Expand Down Expand Up @@ -86,3 +109,52 @@ impl<'r> OpenApiFromRequest<'r> for ScannerAuthGuard {
})
}
}

impl<'r> OpenApiFromRequest<'r> for MatcherAuthGuard {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<rocket_okapi::request::RequestHeaderInput> {
// SRC: https://github.com/GREsau/okapi/blob/1608ff7b92e3daca8cf05aa4594e1cf163e584a9/examples/secure_request_guard/src/api_key.rs
let security_scheme = SecurityScheme {
description: Some("Requires the Matcher's API key to access.".to_owned()),
data: SecuritySchemeData::ApiKey {
name: "x-api-key".to_owned(),
location: "header".to_owned(),
},
extensions: Object::default(),
};
// Add the requirement for this route/endpoint
// This can change between routes.
let mut security_req = SecurityRequirement::new();
// Each security requirement needs to be met before access is allowed.
security_req.insert("ApiKeyAuth".to_owned(), Vec::new());
// These vvvvvvv-----^^^^^^^^^^ values need to match exactly!
Ok(RequestHeaderInput::Security(
"ApiKeyAuth".to_owned(),
security_scheme,
security_req,
))
}

fn get_responses(_gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
Ok(Responses {
// Recommended and most strait forward.
// And easy to add or remove new responses.
responses: okapi::map! {
"400".to_owned() => RefOr::Object(Response {
description: "The Provided API Key or form is wrong or bad."
.to_string(),
..Default::default()
}),
"401".to_owned() => RefOr::Object(Response {
description: "An API Key is missing."
.to_string(),
..Default::default()
}),
},
..Default::default()
})
}
}
8 changes: 7 additions & 1 deletion api/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ fn create_server() -> Rocket<Build> {
if scanner_api_key.is_empty() {
panic!("env variable `SCANNER_API_KEY` is empty.")
}
let matcher_api_key =
env::var("MATCHER_API_KEY").expect("env variable `MATCHER_API_KEY` not set.");
if matcher_api_key.is_empty() {
panic!("env variable `MATCHER_API_KEY` is empty.")
}
let figment = Figment::from(rocket::Config::figment()).merge(Serialized::defaults(Config {
data_folder: data_dir,
scanner_api_key: scanner_api_key,
scanner_api_key,
matcher_api_key,
}));
let rabbit_config = deadpool_amqprs::Config::new(
OpenConnectionArguments::new(
Expand Down
28 changes: 27 additions & 1 deletion api/api/tests/external_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ mod common;

#[cfg(test)]
mod test_external_id {
use std::env;

use api::dto::external_id::NewExternalId;
use rocket::{
http::{ContentType, Status},
http::{ContentType, Header, Status},
serde::uuid,
};

Expand All @@ -31,6 +33,10 @@ mod test_external_id {
let response = client
.post("/external_ids")
.header(ContentType::JSON)
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
assert_eq!(response.status(), Status::Created);
Expand Down Expand Up @@ -80,6 +86,10 @@ mod test_external_id {
let response = client
.post("/external_ids")
.header(ContentType::JSON)
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
assert_eq!(response.status(), Status::Conflict);
Expand All @@ -106,6 +116,10 @@ mod test_external_id {
};
let response = client
.post("/external_ids")
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.header(ContentType::JSON)
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
Expand Down Expand Up @@ -133,6 +147,10 @@ mod test_external_id {
};
let response = client
.post("/external_ids")
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.header(ContentType::JSON)
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
Expand Down Expand Up @@ -161,6 +179,10 @@ mod test_external_id {
let response = client
.post("/external_ids")
.header(ContentType::JSON)
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
assert_eq!(response.status(), Status::Created);
Expand Down Expand Up @@ -208,6 +230,10 @@ mod test_external_id {
};
let response = client
.post("/external_ids")
.header(Header::new(
"X-API-Key",
env::var("MATCHER_API_KEY").unwrap(),
))
.header(ContentType::JSON)
.body(serde_json::to_value(dto.clone()).unwrap().to_string())
.dispatch();
Expand Down

0 comments on commit 2db8c74

Please sign in to comment.