mirror of https://github.com/mastodon/flodgatt
Add support for WHITELIST_MODE (#99)
When the `WHITELIST_MODE` environmental variable is set, Flodgatt requires users to authenticate with a valid access token before subscribing to any timelines (even those that are typically public).
This commit is contained in:
parent
eda52c20b1
commit
a6b4d968cb
|
@ -440,7 +440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flodgatt"
|
name = "flodgatt"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dotenv 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dotenv 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub struct DeploymentConfig<'a> {
|
||||||
pub cors: Cors<'a>,
|
pub cors: Cors<'a>,
|
||||||
pub sse_interval: SseInterval,
|
pub sse_interval: SseInterval,
|
||||||
pub ws_interval: WsInterval,
|
pub ws_interval: WsInterval,
|
||||||
|
pub whitelist_mode: WhitelistMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeploymentConfig<'_> {
|
impl DeploymentConfig<'_> {
|
||||||
|
@ -22,6 +23,7 @@ impl DeploymentConfig<'_> {
|
||||||
unix_socket: Socket::default().maybe_update(env.get("SOCKET")),
|
unix_socket: Socket::default().maybe_update(env.get("SOCKET")),
|
||||||
sse_interval: SseInterval::default().maybe_update(env.get("SSE_FREQ")),
|
sse_interval: SseInterval::default().maybe_update(env.get("SSE_FREQ")),
|
||||||
ws_interval: WsInterval::default().maybe_update(env.get("WS_FREQ")),
|
ws_interval: WsInterval::default().maybe_update(env.get("WS_FREQ")),
|
||||||
|
whitelist_mode: WhitelistMode::default().maybe_update(env.get("WHITELIST_MODE")),
|
||||||
cors: Cors::default(),
|
cors: Cors::default(),
|
||||||
};
|
};
|
||||||
cfg.env = cfg.env.maybe_update(env.get("RUST_ENV"));
|
cfg.env = cfg.env.maybe_update(env.get("RUST_ENV"));
|
||||||
|
|
|
@ -59,6 +59,16 @@ from_env_var!(
|
||||||
let (env_var, allowed_values) = ("PORT", "a number between 0 and 65535".to_string());
|
let (env_var, allowed_values) = ("PORT", "a number between 0 and 65535".to_string());
|
||||||
let from_str = |s| s.parse().ok();
|
let from_str = |s| s.parse().ok();
|
||||||
);
|
);
|
||||||
|
from_env_var!(
|
||||||
|
/// Enables [WHITELIST_MODE](https://docs.joinmastodon.org/admin/config/#whitelist_mode)
|
||||||
|
///
|
||||||
|
/// This mode prevents non-logged in users from subscribing to any timelines
|
||||||
|
/// (including otherwise public timelines).
|
||||||
|
let name = WhitelistMode;
|
||||||
|
let default: bool = false;
|
||||||
|
let (env_var, allowed_values) = ("WHITELIST_MODE", "true or false".to_string());
|
||||||
|
let from_str = |s| s.parse().ok();
|
||||||
|
);
|
||||||
/// Permissions for Cross Origin Resource Sharing (CORS)
|
/// Permissions for Cross Origin Resource Sharing (CORS)
|
||||||
pub struct Cors<'a> {
|
pub struct Cors<'a> {
|
||||||
pub allowed_headers: Vec<&'a str>,
|
pub allowed_headers: Vec<&'a str>,
|
||||||
|
|
|
@ -34,8 +34,8 @@ fn main() {
|
||||||
log::info!("Streaming server initialized and ready to accept connections");
|
log::info!("Streaming server initialized and ready to accept connections");
|
||||||
|
|
||||||
// Server Sent Events
|
// Server Sent Events
|
||||||
let sse_update_interval = *cfg.ws_interval;
|
let (sse_update_interval, whitelist_mode) = (*cfg.sse_interval, *cfg.whitelist_mode);
|
||||||
let sse_routes = sse::extract_user_or_reject(pg_pool.clone())
|
let sse_routes = sse::extract_user_or_reject(pg_pool.clone(), whitelist_mode)
|
||||||
.and(warp::sse())
|
.and(warp::sse())
|
||||||
.map(
|
.map(
|
||||||
move |subscription: subscription::Subscription,
|
move |subscription: subscription::Subscription,
|
||||||
|
@ -57,8 +57,8 @@ fn main() {
|
||||||
.recover(err::handle_errors);
|
.recover(err::handle_errors);
|
||||||
|
|
||||||
// WebSocket
|
// WebSocket
|
||||||
let ws_update_interval = *cfg.ws_interval;
|
let (ws_update_interval, whitelist_mode) = (*cfg.ws_interval, *cfg.whitelist_mode);
|
||||||
let websocket_routes = ws::extract_user_and_token_or_reject(pg_pool.clone())
|
let websocket_routes = ws::extract_user_and_token_or_reject(pg_pool.clone(), whitelist_mode)
|
||||||
.and(warp::ws::ws2())
|
.and(warp::ws::ws2())
|
||||||
.map(
|
.map(
|
||||||
move |subscription: subscription::Subscription, token: Option<String>, ws: Ws2| {
|
move |subscription: subscription::Subscription, token: Option<String>, ws: Ws2| {
|
||||||
|
|
|
@ -39,7 +39,10 @@ macro_rules! parse_query {
|
||||||
.boxed()
|
.boxed()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn extract_user_or_reject(pg_pool: PgPool) -> BoxedFilter<(Subscription,)> {
|
pub fn extract_user_or_reject(
|
||||||
|
pg_pool: PgPool,
|
||||||
|
whitelist_mode: bool,
|
||||||
|
) -> BoxedFilter<(Subscription,)> {
|
||||||
any_of!(
|
any_of!(
|
||||||
parse_query!(
|
parse_query!(
|
||||||
path => "api" / "v1" / "streaming" / "user" / "notification"
|
path => "api" / "v1" / "streaming" / "user" / "notification"
|
||||||
|
@ -67,7 +70,7 @@ pub fn extract_user_or_reject(pg_pool: PgPool) -> BoxedFilter<(Subscription,)> {
|
||||||
// parameter, we need to update our Query if the header has a token
|
// parameter, we need to update our Query if the header has a token
|
||||||
.and(query::OptionalAccessToken::from_sse_header())
|
.and(query::OptionalAccessToken::from_sse_header())
|
||||||
.and_then(Query::update_access_token)
|
.and_then(Query::update_access_token)
|
||||||
.and_then(move |q| Subscription::from_query(q, pg_pool.clone()))
|
.and_then(move |q| Subscription::from_query(q, pg_pool.clone(), whitelist_mode))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,10 @@ impl Default for Subscription {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subscription {
|
impl Subscription {
|
||||||
pub fn from_query(q: Query, pool: PgPool) -> Result<Self, Rejection> {
|
pub fn from_query(q: Query, pool: PgPool, whitelist_mode: bool) -> Result<Self, Rejection> {
|
||||||
let user = match q.access_token.clone() {
|
let user = match q.access_token.clone() {
|
||||||
Some(token) => postgres::select_user(&token, pool.clone())?,
|
Some(token) => postgres::select_user(&token, pool.clone())?,
|
||||||
|
None if whitelist_mode => Err(warp::reject::custom("Error: Invalid access token"))?,
|
||||||
None => UserData::public(),
|
None => UserData::public(),
|
||||||
};
|
};
|
||||||
Ok(Subscription {
|
Ok(Subscription {
|
||||||
|
|
|
@ -34,11 +34,12 @@ fn parse_query() -> BoxedFilter<(Query,)> {
|
||||||
|
|
||||||
pub fn extract_user_and_token_or_reject(
|
pub fn extract_user_and_token_or_reject(
|
||||||
pg_pool: PgPool,
|
pg_pool: PgPool,
|
||||||
|
whitelist_mode: bool,
|
||||||
) -> BoxedFilter<(Subscription, Option<String>)> {
|
) -> BoxedFilter<(Subscription, Option<String>)> {
|
||||||
parse_query()
|
parse_query()
|
||||||
.and(query::OptionalAccessToken::from_ws_header())
|
.and(query::OptionalAccessToken::from_ws_header())
|
||||||
.and_then(Query::update_access_token)
|
.and_then(Query::update_access_token)
|
||||||
.and_then(move |q| Subscription::from_query(q, pg_pool.clone()))
|
.and_then(move |q| Subscription::from_query(q, pg_pool.clone(), whitelist_mode))
|
||||||
.and(query::OptionalAccessToken::from_ws_header())
|
.and(query::OptionalAccessToken::from_ws_header())
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue