diff --git a/aw-server/src/config.rs b/aw-server/src/config.rs index 8fbc13e7..c3183a03 100644 --- a/aw-server/src/config.rs +++ b/aw-server/src/config.rs @@ -25,6 +25,8 @@ pub struct AWConfig { pub port: u16, #[serde(skip, default = "default_testing")] pub testing: bool, // This is not written to the config file (serde(skip)) + #[serde(default = "default_apikey")] + pub apikey: Option, #[serde(default = "default_cors")] pub cors: Vec, } @@ -35,6 +37,7 @@ impl Default for AWConfig { address: default_address(), port: default_port(), testing: default_testing(), + apikey: default_apikey(), cors: default_cors(), } } @@ -72,6 +75,10 @@ fn default_testing() -> bool { is_testing() } +fn default_apikey() -> Option { + None +} + fn default_port() -> u16 { if is_testing() { 5666 diff --git a/aw-server/src/endpoints/auth.rs b/aw-server/src/endpoints/auth.rs new file mode 100644 index 00000000..5bc696ba --- /dev/null +++ b/aw-server/src/endpoints/auth.rs @@ -0,0 +1,51 @@ +/// On most systems we do not concern ourselves with local security [1], however on Android +/// apps have their storage isolated yet ActivityWatch happily exposes its API and database +/// through the HTTP API when the server is running. This could be considered a severe +/// security flaw, and fixing it would significantly improve security on Android (as mentioned in [2]). +/// +/// Requiring an API key can also be useful in other scenarios where an extra level of security is +/// desired. +/// +/// Based on the ApiKey example at [3]. +/// +/// [1]: https://docs.activitywatch.net/en/latest/security.html#activitywatch-is-only-as-secure-as-your-system +/// [2]: https://forum.activitywatch.net/t/rest-api-supported-with-android-version-of-activity-watch/854/6?u=erikbjare +/// [3]: https://api.rocket.rs/v0.4/rocket/request/trait.FromRequest.html +use rocket::http::Status; +use rocket::request::{self, FromRequest, Request}; +use rocket::{Outcome, State}; + +use crate::config::AWConfig; + +struct ApiKey(Option); + +#[derive(Debug)] +enum ApiKeyError { + BadCount, + Missing, + Invalid, +} + +// TODO: Use guard on endpoints +// TODO: Add tests for protected endpoints (important to ensure security) +impl<'a, 'r> FromRequest<'a, 'r> for ApiKey { + type Error = ApiKeyError; + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + // TODO: How will this key be configured by the user? + let config = request.guard::>().unwrap(); + match &config.apikey { + None => Outcome::Success(ApiKey(None)), + Some(apikey) => { + // TODO: How will this header be set in the browser? + let keys: Vec<_> = request.headers().get("x-api-key").collect(); + match keys.len() { + 0 => Outcome::Failure((Status::BadRequest, ApiKeyError::Missing)), + 1 if apikey == keys[0] => Outcome::Success(ApiKey(Some(keys[0].to_string()))), + 1 => Outcome::Failure((Status::BadRequest, ApiKeyError::Invalid)), + _ => Outcome::Failure((Status::BadRequest, ApiKeyError::BadCount)), + } + } + } + } +} diff --git a/aw-server/src/endpoints/cors.rs b/aw-server/src/endpoints/cors.rs index 679be1b2..7dcc4494 100644 --- a/aw-server/src/endpoints/cors.rs +++ b/aw-server/src/endpoints/cors.rs @@ -1,3 +1,14 @@ +/// Cross-Origin Resource Sharing is a way to specify the permissions websites that do not share the origin have. +/// +/// However, it's a check done by the browser when a response has already been received (but before it's accessible by the origin site). +/// As such, it does *not* protect against all kinds of cross-origin attacks. As an example, a GET +/// requests which has side-effects will still have such effects, even though the response was +/// blocked by the browser. +/// +/// In many cases, pre-flight requests are made to check CORS before the real request is made. +/// +/// For more info, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +/// use rocket::http::Method; use rocket_cors::{AllowedHeaders, AllowedOrigins}; diff --git a/aw-server/src/endpoints/mod.rs b/aw-server/src/endpoints/mod.rs index 65bfa534..ca31ea40 100644 --- a/aw-server/src/endpoints/mod.rs +++ b/aw-server/src/endpoints/mod.rs @@ -19,6 +19,7 @@ pub struct ServerState { #[macro_use] mod util; +mod auth; mod bucket; mod cors; mod export;