feat: Initialize forgejo-guardian

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-11-14 19:14:38 +00:00
parent 28c828f06d
commit c41bf5c940
Signed by: awiteb
GPG key ID: 3F6B55640AA6682F
16 changed files with 2453 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
forgejo-guardian.toml

1640
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

32
Cargo.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "forgejo-guardian"
description = "Simple Forgejo instance guardian, banning users and alerting admins based on certain regular expressions"
version = "0.1.0"
edition = "2021"
authors = ["Awiteb <a@4rs.nl>"]
repository = "https://git.4rs.nl/awiteb/forgejo-guardian"
license = "AGPL-3.0-or-later"
[dependencies]
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
toml = "0.8.19"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
thiserror = "2.0.2"
regex = "1.11.1"
reqwest = { version = "0.12.9", default-features = false, features = [
"charset",
"http2",
"rustls-tls",
] }
tokio-util = { version = "0.7.12", default-features = false }
tokio = { version = "1.41.1", default-features = false, features = [
"rt-multi-thread",
"macros",
"sync",
"signal",
] }
url = { version = "2.5.3", default-features = false, features = ["serde"] }

100
README.md Normal file
View file

@ -0,0 +1,100 @@
<div align="center">
# Forgejo Guardian
Simple Forgejo instance guardian, banning users and alerting admins based on certain regular expressions (regex)
<!-- [![Forgejo CI Status](https://git.4rs.nl/awiteb/forgejo-guardian/badges/workflows/ci.yml/badge.svg)](https://git.4rs.nl/awiteb/forgejo-guardian)
[![Forgejo CD Status](https://git.4rs.nl/awiteb/forgejo-guardian/badges/workflows/cd.yml/badge.svg)](https://git.4rs.nl/awiteb/forgejo-guardian) -->
[![agplv3-or-later](https://www.gnu.org/graphics/agplv3-88x31.png)](https://www.gnu.org/licenses/agpl-3.0.html)
</div>
## Installation
You can let [cargo](https://doc.rust-lang.org/cargo/) install the binary for you, or build it yourself. <!-- You can also download the pre-built binaries from the [releases](https://git.4rs.nl/awiteb/forgejo-guardian/releases) page. -->
### Build it
#### `cargo-install`
> [!TIP]
> This will install the binary in `~/.cargo/bin/forgejo-guardian`. Make sure to add this directory to your `PATH`.
> If you want to update it, run `cargo install ...` again.
```sh
cargo install --git https://git.4rs.nl/awiteb/forgejo-guardian
```
#### `cargo-install` (from source)
> [!TIP]
> Then when you want to update it, pull the changes and run `cargo install --path .` again.
```sh
git clone https://git.4rs.nl/awiteb/forgejo-guardian
cd forgejo-guardian
cargo install --path .
```
#### Build (from source)
> [!TIP]
> The binary will be in `./target/release/forgejo-guardian`.
```sh
git clone https://git.4rs.nl/awiteb/forgejo-guardian
cd forgejo-guardian
cargo build --release
```
## Configuration
We use `TOML` format for configuration, the default configuration file is `/app/forgejo-guardian.toml`, but you can specify a different one with `FORGEJO_GUARDIAN_CONFIG` environment variable.
### Structure
In our configuration file, we have two main sections:
- `forgejo`: Forgejo instance configuration
- `expressions`: Regular expressions to match against
<!-- - `telegram`: Telegram bot configuration -->
#### `forgejo`
Forgejo configuration section, with the following fields:
- `instance_url`: Forgejo instance URL (must be HTTPS or HTTP)
- `token`: Token to use to get the new users and ban them, requires `read:admin` and `write:admin` scopes.
```toml
[forgejo]
instance_url = "https://forgejo.example
token = "your-token"
```
#### `expressions`
Expressions configuration section, with the following fields:
- `ban`: Regular expressions to match against to ban the user
- `sus`: Regular expressions to match against to alert the admins
`ban` and `sus` are tables, and each one have the following fields:
- `usernames`: Regular expressions to match against the usernames
- `full_names`: Regular expressions to match against the full names
- `biographies`: Regular expressions to match against the biographies
- `emails`: Regular expressions to match against the emails
- `websites`: Regular expressions to match against the websites
- `locations`: Regular expressions to match against the locations
```toml
[expressions.ban]
usernames = ['^admin.*$']
[expressions.sus]
usernames = ['^mod.*$']
```

13
rust-toolchain.toml Normal file
View file

@ -0,0 +1,13 @@
[toolchain]
# We use nightly in development only, the project will always be compliant with
# the latest stable release and the MSRV as defined in `Cargo.toml` file.
channel = "nightly-2024-11-12"
components = [
"rustc",
"cargo",
"rust-std",
"rust-src",
"rustfmt",
"rust-analyzer",
"clippy",
]

21
rustfmt.toml Normal file
View file

@ -0,0 +1,21 @@
unstable_features = true
style_edition = "2021"
combine_control_expr = false
wrap_comments = true
condense_wildcard_suffixes = true
edition = "2021"
enum_discrim_align_threshold = 20
force_multiline_blocks = true
format_code_in_doc_comments = true
format_generated_files = false
format_macro_matchers = true
format_strings = true
imports_layout = "HorizontalVertical"
newline_style = "Unix"
normalize_comments = true
reorder_impl_items = true
group_imports = "StdExternalCrate"
single_line_let_else_max_width = 0
struct_field_align_threshold = 20
use_try_shorthand = true

132
src/config.rs Normal file
View file

@ -0,0 +1,132 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
/// The environment variable of the config file path
pub(crate) const CONFIG_PATH_ENV: &str = "FORGEJO_GUARDIAN_CONFIG";
/// Defult config path location
pub(crate) const DEFAULT_CONFIG_PATH: &str = "/app/forgejo-guardian.toml";
use regex::Regex;
use serde::{de, Deserialize};
use url::Url;
/// Deserialize a string into a `url::Url`
///
/// This will check if the url is `http` or `https` and if it is a valid url
fn deserialize_str_url<'de, D>(deserializer: D) -> Result<Url, D::Error>
where
D: de::Deserializer<'de>,
{
let url = Url::parse(&String::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(e.to_string()))?;
if url.scheme() != "http" && url.scheme() != "https" {
return Err(de::Error::custom("URL scheme must be http or https"));
}
Ok(url)
}
/// Deserialize a vector of strings into a vector of `regex::Regex`
fn deserialize_regex_vec<'de, D>(deserializer: D) -> Result<Vec<Regex>, D::Error>
where
D: de::Deserializer<'de>,
{
Vec::<String>::deserialize(deserializer)?
.into_iter()
.map(|s| Regex::new(&s))
.collect::<Result<_, _>>()
.map_err(|e| de::Error::custom(e.to_string()))
}
/// The forgejo config of the guard
#[derive(Deserialize)]
pub struct Forgejo {
/// The bot token
///
/// Required Permissions:
/// - `read:admin`: To list the users
/// - `write:admin`: To ban the users
pub token: String,
/// The instance, e.g. `https://example.com` or `https://example.com/` or `http://example.com:8080`
#[serde(rename = "instance_url", deserialize_with = "deserialize_str_url")]
pub instance: Url,
}
/// The expression
#[derive(Deserialize, Debug, Default)]
pub struct Expr {
/// The regular expressions that the action will be performed if they are
/// present in the username
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub usernames: Vec<Regex>,
/// The regular expressions that the action will be performed if they are
/// present in the user full_name
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub full_names: Vec<Regex>,
/// The regular expressions that the action will be performed if they are
/// present in the user biography
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub biographies: Vec<Regex>,
/// The regular expressions that the action will be performed if they are
/// present in the user email
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub emails: Vec<Regex>,
/// The regular expressions that the action will be performed if they are
/// present in the user website
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub websites: Vec<Regex>,
/// The regular expressions that the action will be performed if they are
/// present in the user location
#[serde(default)]
#[serde(deserialize_with = "deserialize_regex_vec")]
pub locations: Vec<Regex>,
}
/// the expressions
#[derive(Deserialize, Debug, Default)]
pub struct Exprs {
/// Direct ban expressions.
///
/// Users are directly banned if any of the expressions are true
#[serde(default)]
pub ban: Expr,
/// Alert expressions.
///
/// Moderators will be notified via Telegram if one of the expressions are
/// true
#[serde(default)]
pub sus: Expr,
}
/// forgejo-guard configuration
#[derive(Deserialize)]
pub struct Config {
/// Configuration for the forgejo guard itself
pub forgejo: Forgejo,
/// The expressions, which are used to determine the actions
#[serde(default)]
pub expressions: Exprs,
}

42
src/error.rs Normal file
View file

@ -0,0 +1,42 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use crate::config::{CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH};
/// Result of the guard
pub type GuardResult<T> = Result<T, GuardError>;
#[derive(Debug, thiserror::Error)]
pub enum GuardError {
/// IO errors
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// reqwest error
#[error("Sending request error: {0}")]
RequestError(#[from] reqwest::Error),
/// Invalid response from Forgejo
#[error("Invalid response from Forgejo, the error `{0:?}` request `{1:?}`")]
InvalidForgejoResponse(String, reqwest::Request),
/// Faild to get the config file
#[error(
"The configuration file could not be accessed, its path is not in the `{CONFIG_PATH_ENV}` \
environment variable nor is it in the default path `{DEFAULT_CONFIG_PATH}`"
)]
CantGetConfigFile,
/// Faild to deserialize the config file
#[error("Failed to deserialize the config: {0}")]
FaildDeserializeConfig(#[from] toml::de::Error),
}

View file

@ -0,0 +1,40 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use reqwest::Method;
use crate::error::GuardResult;
/// Ban a user from the instance, purging their data.
pub async fn ban_user(
client: &reqwest::Client,
instance: &url::Url,
token: &str,
username: &str,
) -> GuardResult<()> {
let res = client
.execute(super::build_request(
Method::DELETE,
instance,
token,
&format!("/api/v1/admin/users/{username}?purge=true"),
))
.await?;
tracing::debug!("Ban user response: {:?}", &res);
tracing::debug!("Body: {}", res.text().await.unwrap_or_default());
Ok(())
}

View file

@ -0,0 +1,44 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use reqwest::Method;
use super::ForgejoUser;
use crate::error::{GuardError, GuardResult};
/// Returns the first page of users from the instance
pub async fn get_users(
client: &reqwest::Client,
instance: &url::Url,
token: &str,
) -> GuardResult<Vec<ForgejoUser>> {
let req = super::build_request(Method::GET, instance, token, "/api/v1/admin/users");
let res = client
.execute(req.try_clone().expect("There is no body"))
.await?;
if !res.status().is_success() {
return Err(GuardError::InvalidForgejoResponse(
format!("Status code: {status}", status = res.status()),
req,
));
}
tracing::debug!("Get users response: {res:?}");
serde_json::from_str(&res.text().await?)
.map_err(|err| GuardError::InvalidForgejoResponse(err.to_string(), req))
}

41
src/forgejo_api/mod.rs Normal file
View file

@ -0,0 +1,41 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
//! Simple SDK for Forgejo API, only for banning users and getting users.
mod ban_user;
mod get_users;
mod user;
pub use ban_user::*;
pub use get_users::*;
use reqwest::{Method, Request};
pub use user::*;
/// Build a request with the given method, instance, token and endpoint.
pub fn build_request(method: Method, instance: &url::Url, token: &str, endpoint: &str) -> Request {
let url = instance.join(endpoint).unwrap();
let mut req = Request::new(method, url);
req.headers_mut().insert(
"Authorization",
format!("token {token}").try_into().expect("Is valid"),
);
req.headers_mut()
.insert("accept", "application/json".try_into().expect("Is valid"));
req
}

44
src/forgejo_api/user.rs Normal file
View file

@ -0,0 +1,44 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use serde::Deserialize;
/// Forgejo user
#[derive(Deserialize, Debug)]
pub struct ForgejoUser {
/// User id, incremental integer
pub id: usize,
/// Avatar URL
pub avatar_url: url::Url,
/// HTML URL
pub html_url: url::Url,
/// Is admin
pub is_admin: bool,
/// Username
#[serde(rename = "login")]
pub username: String,
/// Full name
pub full_name: String,
/// Biography (AKA bio, profile description)
#[serde(rename = "description")]
pub biography: String,
/// Email
pub email: String,
/// Website
pub website: String,
/// Location
pub location: String,
}

72
src/main.rs Normal file
View file

@ -0,0 +1,72 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use std::{process::ExitCode, sync::Arc, time::Duration};
use tokio::{signal::ctrl_c, sync};
use tokio_util::sync::CancellationToken;
pub mod config;
pub mod error;
pub mod forgejo_api;
pub mod traits;
pub mod users_fetcher;
pub mod utils;
async fn try_main() -> error::GuardResult<()> {
let config = Arc::new(utils::get_config()?);
let cancellation_token = CancellationToken::new();
// Suspicious users are sent and received in this channel, users who meet the
// `alert` expressions
let (sus_sender, _sus_receiver) = sync::mpsc::channel::<forgejo_api::ForgejoUser>(100);
tracing::info!("The instance: {}", config.forgejo.instance);
tracing::debug!("The config exprs: {:#?}", config.expressions);
tokio::spawn(users_fetcher::users_fetcher(
Arc::clone(&config),
cancellation_token.clone(),
sus_sender.clone(),
));
// TODO: Sus worker, who will receive sus users
tokio::select! {
_ = ctrl_c() => {
cancellation_token.cancel();
}
_ = cancellation_token.cancelled() => {}
};
tracing::info!("Waiting for graceful shutdown");
tokio::time::sleep(Duration::from_secs(3)).await;
Ok(())
}
#[tokio::main]
async fn main() -> ExitCode {
tracing_subscriber::fmt()
.with_max_level(utils::get_log_level())
.init();
if let Err(err) = try_main().await {
eprintln!("{err}");
return ExitCode::FAILURE;
}
ExitCode::SUCCESS
}

42
src/traits.rs Normal file
View file

@ -0,0 +1,42 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use regex::Regex;
use crate::{config::Expr, forgejo_api::ForgejoUser};
/// Trait for checking if a user matches one of the expressions
pub trait ExprChecker {
/// Returns the first matching expression, if any
fn is_match(&self, user: &ForgejoUser) -> Option<Regex>;
}
impl ExprChecker for Expr {
fn is_match<'a>(&'a self, user: &ForgejoUser) -> Option<Regex> {
let one_of = |hay, exprs: &'a Vec<Regex>| exprs.iter().find(|re| re.is_match(hay));
[
one_of(&user.username, &self.usernames),
one_of(&user.full_name, &self.full_names),
one_of(&user.biography, &self.biographies),
one_of(&user.email, &self.emails),
one_of(&user.website, &self.websites),
one_of(&user.location, &self.locations),
]
.into_iter()
.find_map(|v| v)
.cloned()
}
}

142
src/users_fetcher.rs Normal file
View file

@ -0,0 +1,142 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
use tokio::sync::mpsc::Sender;
use tokio_util::sync::CancellationToken;
use crate::{
config::Config,
error::GuardResult,
forgejo_api::{self, get_users, ForgejoUser},
traits::ExprChecker,
};
/// Get the new instance users, the vector may be empty if there are no new users
///
/// Forgejo use intger ids for the users, so we can use the last user id to get
/// the new users.
async fn get_new_users(
request_client: &reqwest::Client,
last_user_id: usize,
config: &Config,
) -> GuardResult<Vec<ForgejoUser>> {
Ok(get_users(
request_client,
&config.forgejo.instance,
&config.forgejo.token,
)
.await?
.into_iter()
.filter(|u| u.id > last_user_id)
.collect())
}
/// Check if ban or suspect a new user
async fn check_new_user(
user: ForgejoUser,
request_client: &reqwest::Client,
config: &Config,
sus_sender: &Sender<ForgejoUser>,
) {
if let Some(re) = config.expressions.ban.is_match(&user) {
tracing::info!("@{} has been banned because `{re}`", user.username);
if let Err(err) = forgejo_api::ban_user(
request_client,
&config.forgejo.instance,
&config.forgejo.token,
&user.username,
)
.await
{
tracing::error!("Error while banning a user: {err}");
}
} else if let Some(re) = config.expressions.sus.is_match(&user) {
tracing::info!("@{} has been suspected because `{re}`", user.username);
let _ = sus_sender.send(user).await;
}
}
/// Check for new users and send the suspected users to the channel and ban the
/// banned users
async fn check_new_users(
last_user_id: Arc<AtomicUsize>,
sus_sender: Sender<ForgejoUser>,
request_client: Arc<reqwest::Client>,
config: Arc<Config>,
) {
match get_new_users(
&request_client,
last_user_id.load(Ordering::Relaxed),
&config,
)
.await
{
Ok(new_users) => {
if !new_users.is_empty() {
tracing::debug!("Found {} new user(s)", new_users.len());
}
if let Some(uid) = new_users.iter().max_by_key(|u| u.id).map(|u| u.id) {
tracing::debug!("New last user id: {uid}");
last_user_id.store(uid, Ordering::Relaxed)
}
for user in new_users {
check_new_user(user, &request_client, &config, &sus_sender).await;
}
}
Err(err) => {
tracing::error!("Error while fetching new users: {err}");
}
}
}
/// The users fetcher, it will check for new users every period and send the
/// suspected users to the channel
pub async fn users_fetcher(
config: Arc<Config>,
cancellation_token: CancellationToken,
sus_sender: Sender<ForgejoUser>,
) {
let last_user_id = Arc::new(AtomicUsize::new(0));
let request_client = Arc::new(reqwest::Client::new());
tracing::info!("Starting users fetcher");
loop {
tokio::select! {
_ = tokio::time::sleep(Duration::from_secs(120)) => {
tokio::spawn(check_new_users(
Arc::clone(&last_user_id),
sus_sender.clone(),
Arc::clone(&request_client),
Arc::clone(&config),
));
}
_ = cancellation_token.cancelled() => {
tracing::info!("Users fetcher has been stopped successfully.");
break
}
};
}
}

46
src/utils.rs Normal file
View file

@ -0,0 +1,46 @@
// Simple Forgejo instance guardian, banning users and alerting admins based on
// certain regular expressions. Copyright (C) 2024 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl.txt>.
use std::{fs, path::PathBuf, str::FromStr};
use tracing::level_filters::LevelFilter;
use crate::{
config::{Config, CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH},
error::{GuardError, GuardResult},
};
/// Returns the log level from `RUST_LOG` environment variable
pub fn get_log_level() -> LevelFilter {
std::env::var("RUST_LOG")
.ok()
.and_then(|s| LevelFilter::from_str(s.as_str()).ok())
.unwrap_or(LevelFilter::INFO)
}
/// Returns the guard config
pub fn get_config() -> GuardResult<Config> {
let config_path = if let Ok(path) = std::env::var(CONFIG_PATH_ENV) {
PathBuf::from(path)
} else if matches!(fs::exists(DEFAULT_CONFIG_PATH), Ok(true)) {
PathBuf::from(DEFAULT_CONFIG_PATH)
} else {
return Err(GuardError::CantGetConfigFile);
};
tracing::info!("Config path: {}", config_path.display());
toml::from_str(&fs::read_to_string(&config_path)?).map_err(Into::into)
}