add: tool to delete all accounts that except allow listed
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
2012
Cargo.lock
generated
Normal file
2012
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "gitea-tools"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
clap = { version = "4.5.47", features = ["derive"] }
|
||||
dialoguer = "0.12.0"
|
||||
env_logger = "0.11.8"
|
||||
lazy_static = "1.5.0"
|
||||
log = "0.4.28"
|
||||
reqwest = { version = "0.12.23", features=["json"]}
|
||||
serde = { version = "1.0.220", features = ["derive"] }
|
||||
serde_json = "1.0.144"
|
||||
tokio = { version = "1.47.1", features = ["full"] }
|
||||
tokio-task-pool = { version = "0.1.5", features = ["log"] }
|
||||
72
src/lib.rs
Normal file
72
src/lib.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct GetTokenResp {
|
||||
pub name: String,
|
||||
pub sha1: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Email {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub verified: bool
|
||||
}
|
||||
|
||||
pub async fn get_token(
|
||||
client: &reqwest::Client,
|
||||
router: &Router,
|
||||
username: &str,
|
||||
password: &str,
|
||||
otp: Option<&str>,
|
||||
) -> Result<GetTokenResp, anyhow::Error> {
|
||||
let mut req = client
|
||||
.post(router.create_token(username))
|
||||
.basic_auth(username, Some(password))
|
||||
.json(&json!({
|
||||
"name": "gitea-tools",
|
||||
"scopes": ["write:admin", "read:admin"],
|
||||
}))
|
||||
.header(CONTENT_TYPE, "application/json");
|
||||
|
||||
if let Some(otp) = otp {
|
||||
req = req.header("X-Gitea-OTP", otp);
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
let resp = resp.json::<GetTokenResp>().await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn list_emails(client: &reqwest::Client, router: &Router) -> Result<Vec<Email>, anyhow::Error> {
|
||||
Ok(client.get(&router.list_emails()).send().await?.json::<Vec<Email>>().await?)
|
||||
}
|
||||
|
||||
pub async fn purge_user(client: &reqwest::Client, router: &Router, username: &str) -> Result<(), anyhow::Error> {
|
||||
client.delete(router.delete_user(username) + "?purge=true").send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Router {
|
||||
base: String
|
||||
}
|
||||
|
||||
impl Router {
|
||||
pub fn new(base: String) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
|
||||
fn create_token(&self, username: &str) -> String {
|
||||
format!("{}/api/v1/users/{username}/tokens", self.base)
|
||||
}
|
||||
|
||||
fn list_emails(&self) -> String {
|
||||
format!("{}/api/v1/admin/emails", self.base)
|
||||
}
|
||||
|
||||
fn delete_user(&self, username: &str) -> String {
|
||||
format!("{}/api/v1/admin/users/{username}", self.base)
|
||||
}
|
||||
}
|
||||
100
src/main.rs
Normal file
100
src/main.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use clap::{Parser, arg};
|
||||
use dialoguer::{Input, Password};
|
||||
use gitea_tools::{Router, list_emails, purge_user};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use reqwest::{ClientBuilder, header};
|
||||
use tokio_task_pool::Pool;
|
||||
|
||||
lazy_static! {
|
||||
static ref ALLOW_LIST: HashSet<&'static str> = {
|
||||
let mut m = HashSet::new();
|
||||
m.insert("charles@tipsy.codes");
|
||||
m.insert("gitea@local.domain");
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
username: Option<String>,
|
||||
#[arg(short, long)]
|
||||
password: Option<String>,
|
||||
#[arg(short, long)]
|
||||
token: Option<String>,
|
||||
#[arg(short, long)]
|
||||
gitea_address: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
let args = Args::parse();
|
||||
|
||||
let router = Router::new(args.gitea_address);
|
||||
|
||||
let token: String;
|
||||
if let Some(t) = args.token {
|
||||
token = t;
|
||||
} else {
|
||||
let client = reqwest::Client::new();
|
||||
let username: String;
|
||||
if let Some(u) = args.username {
|
||||
username = u;
|
||||
} else {
|
||||
username = Input::new()
|
||||
.with_prompt("Gitea username")
|
||||
.interact_text()
|
||||
.unwrap();
|
||||
}
|
||||
let password: String;
|
||||
if let Some(p) = args.password {
|
||||
password = p;
|
||||
} else {
|
||||
password = Password::new()
|
||||
.with_prompt("Gitea password (no echo)")
|
||||
.interact()
|
||||
.unwrap();
|
||||
}
|
||||
token = ::gitea_tools::get_token(&client, &router, &username, &password, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.sha1;
|
||||
}
|
||||
|
||||
let mut headers = header::HeaderMap::new();
|
||||
let mut auth_value = header::HeaderValue::from_str(&format!("token {token}")).unwrap();
|
||||
auth_value.set_sensitive(true);
|
||||
headers.insert(header::AUTHORIZATION, auth_value);
|
||||
|
||||
let client = ClientBuilder::new()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Get a list of active emails
|
||||
let pool = Pool::bounded(10);
|
||||
loop {
|
||||
let emails = list_emails(&client, &router).await.unwrap();
|
||||
if emails.len() == ALLOW_LIST.len() {
|
||||
break;
|
||||
}
|
||||
for email in emails {
|
||||
if ALLOW_LIST.contains(&email.email as &str) {
|
||||
continue;
|
||||
}
|
||||
let username = email.username;
|
||||
let client = client.clone();
|
||||
let router = router.clone();
|
||||
pool.spawn(async move {
|
||||
info!("Deleting {}", username);
|
||||
purge_user(&client, &router, &username).await.unwrap();
|
||||
info!("Deleted {}!", username);
|
||||
}).await.unwrap();
|
||||
}
|
||||
}
|
||||
// Do we need to wait for pool to finish?
|
||||
}
|
||||
Reference in New Issue
Block a user