Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
1988
Cargo.lock
generated
Normal file
1988
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "matrix-admin-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.60", features = ["derive"] }
|
||||||
|
colored = "3.1.1"
|
||||||
|
enum-iterator = "2.3.0"
|
||||||
|
reqwest = { version = "0.13.2", features = ["blocking", "form"] }
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
shlex = "1.3.0"
|
||||||
3
deactivate.json
Normal file
3
deactivate.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"skip_erase": false
|
||||||
|
}
|
||||||
4
newuser.json
Normal file
4
newuser.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"username": "jp",
|
||||||
|
"skip_homeserver_check": true
|
||||||
|
}
|
||||||
0
oauthlinks.json
Normal file
0
oauthlinks.json
Normal file
7
postbody.json
Normal file
7
postbody.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"password": "whatever",
|
||||||
|
"admin": false,
|
||||||
|
"deactivated": false,
|
||||||
|
"user_type": null,
|
||||||
|
"locked": false
|
||||||
|
}
|
||||||
4
setpassword.json
Normal file
4
setpassword.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"password": "1wshreb",
|
||||||
|
"skip_password_check": true
|
||||||
|
}
|
||||||
210
src/app.rs
Normal file
210
src/app.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use colored::{ColoredString, Colorize};
|
||||||
|
use reqwest::{StatusCode, Url};
|
||||||
|
|
||||||
|
use crate::json_types::*;
|
||||||
|
|
||||||
|
const MAS_SERVER_BASE_URL: &str = "https://mas.supersaturn.space";
|
||||||
|
const USERS_ENDPT: &str = "/api/admin/v1/users";
|
||||||
|
const OAUTH_LINKS_ENDPT: &str = "/api/admin/v1/upstream-oauth-links";
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct App {
|
||||||
|
matrix_base_url: String,
|
||||||
|
mas_base_url: String,
|
||||||
|
auth_metadata: Option<AuthMetadata>,
|
||||||
|
client_registration: Option<ClientRegistration>,
|
||||||
|
auth_token: Option<AuthorizationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(matrix_base_url: &str, mas_base_url: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
matrix_base_url: matrix_base_url.to_owned(),
|
||||||
|
mas_base_url: mas_base_url.to_owned(),
|
||||||
|
auth_metadata: None,
|
||||||
|
client_registration: None,
|
||||||
|
auth_token: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_status(&self) {
|
||||||
|
println!("\nApp Base URL: {}", self.matrix_base_url);
|
||||||
|
println!(
|
||||||
|
"Auth Metadata? {}",
|
||||||
|
quick_format_bool(self.auth_metadata.is_some())
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Client Reg? {}",
|
||||||
|
quick_format_bool(self.client_registration.is_some())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_auth_metadata(&mut self) {
|
||||||
|
const METADATA_ENDPT: &str = "/_matrix/client/unstable/org.matrix.msc2965/auth_metadata";
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.get(format!("{}{METADATA_ENDPT}", self.matrix_base_url))
|
||||||
|
.header("Accept", "application/json");
|
||||||
|
|
||||||
|
let res = req.send().unwrap();
|
||||||
|
|
||||||
|
let Ok(metadata) = serde_json::from_str::<AuthMetadata>(&res.text().unwrap()) else {
|
||||||
|
panic!("Could not get metadata...");
|
||||||
|
};
|
||||||
|
|
||||||
|
// println!("{}", serde_json::to_string_pretty(&metadata).unwrap());
|
||||||
|
|
||||||
|
self.auth_metadata = Some(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_client(&mut self) {
|
||||||
|
let auth_metadata = self
|
||||||
|
.auth_metadata
|
||||||
|
.as_ref()
|
||||||
|
.expect("Can't register client without getting auth metadata");
|
||||||
|
|
||||||
|
let crr = ClientRegisterReq {
|
||||||
|
client_name: "JP CLI Tool".to_owned(),
|
||||||
|
client_uri: "https://myclitool.supersaturn.space/".to_owned(),
|
||||||
|
grant_types: vec![
|
||||||
|
"urn:ietf:params:oauth:grant-type:device_code".to_owned(),
|
||||||
|
"refresh_token".to_owned(),
|
||||||
|
],
|
||||||
|
application_type: "native".to_owned(),
|
||||||
|
token_endpoint_auth_method: "none".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&crr).unwrap();
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.post(auth_metadata.registration_endpoint.as_str())
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(json);
|
||||||
|
|
||||||
|
let res = req.send().unwrap();
|
||||||
|
|
||||||
|
let Ok(crres) = serde_json::from_str::<ClientRegistration>(&res.text().unwrap()) else {
|
||||||
|
panic!("Could not get metadata...");
|
||||||
|
};
|
||||||
|
|
||||||
|
// println!("{}", serde_json::to_string_pretty(&crres).unwrap());
|
||||||
|
|
||||||
|
self.client_registration = Some(crres);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authorize_device(&mut self) {
|
||||||
|
let auth_metadata = self
|
||||||
|
.auth_metadata
|
||||||
|
.as_ref()
|
||||||
|
.expect("Can't authorize device without auth metadata");
|
||||||
|
|
||||||
|
let client_registration = self
|
||||||
|
.client_registration
|
||||||
|
.as_ref()
|
||||||
|
.expect("Can't authorize device without client registration");
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.post(&auth_metadata.device_authorization_endpoint)
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.form(&[
|
||||||
|
("client_id", client_registration.client_id.as_str()),
|
||||||
|
(
|
||||||
|
"scope",
|
||||||
|
"urn:mas:admin urn:synapse:admin:* urn:matrix:client:api:*",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// println!("Req: {:?}\n\n", req);
|
||||||
|
|
||||||
|
let res = req.send().unwrap();
|
||||||
|
|
||||||
|
let Ok(device_grant) = serde_json::from_str::<DeviceGrant>(&res.text().unwrap()) else {
|
||||||
|
panic!("Could not get device grant")
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Device Grant: {:?}", device_grant);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let expiry = Duration::from_secs(device_grant.expires_in);
|
||||||
|
let interval = Duration::from_secs(device_grant.interval);
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(interval);
|
||||||
|
|
||||||
|
if start.elapsed() > expiry {
|
||||||
|
println!("Request expired!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.post(&auth_metadata.token_endpoint)
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.form(&[
|
||||||
|
("client_id", client_registration.client_id.as_str()),
|
||||||
|
("device_code", device_grant.device_code.as_str()),
|
||||||
|
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = req.send().unwrap();
|
||||||
|
let status = res.status();
|
||||||
|
let res_text = res.text().unwrap();
|
||||||
|
|
||||||
|
match status {
|
||||||
|
StatusCode::OK => {
|
||||||
|
let token = serde_json::from_str::<AuthorizationToken>(&res_text)
|
||||||
|
.expect("Couldn't decode auth token");
|
||||||
|
println!("{:?}", token);
|
||||||
|
println!("Authorized!");
|
||||||
|
self.auth_token = Some(token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("...\n\n{}\n\n...", res_text);
|
||||||
|
println!("Waiting for auth...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_oath_links(&self) {
|
||||||
|
let auth_token = self
|
||||||
|
.auth_token
|
||||||
|
.as_ref()
|
||||||
|
.expect("Need auth token to perform this action");
|
||||||
|
|
||||||
|
let url = format!("{}{OAUTH_LINKS_ENDPT}", self.mas_base_url);
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.get(url)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header(
|
||||||
|
"Authorization",
|
||||||
|
format!("Bearer {}", auth_token.access_token.clone()),
|
||||||
|
)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(include_str!("../oauthlinks.json"));
|
||||||
|
|
||||||
|
println!("Req {:?}", req);
|
||||||
|
|
||||||
|
let res = req.send().unwrap();
|
||||||
|
|
||||||
|
println!("{}", res.text().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quick_format_bool(b: bool) -> ColoredString {
|
||||||
|
match b {
|
||||||
|
true => "true".bright_green(),
|
||||||
|
false => "false".red(),
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/json_types.rs
Normal file
55
src/json_types.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct ClientRegistration {
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_id_issued_at: u64,
|
||||||
|
pub grant_types: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct ClientRegisterReq {
|
||||||
|
pub client_name: String,
|
||||||
|
pub client_uri: String,
|
||||||
|
pub grant_types: Vec<String>,
|
||||||
|
pub application_type: String,
|
||||||
|
pub token_endpoint_auth_method: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct AuthMetadata {
|
||||||
|
pub device_authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub registration_endpoint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct DeviceGrant {
|
||||||
|
pub device_code: String,
|
||||||
|
pub user_code: String,
|
||||||
|
pub verification_uri: String,
|
||||||
|
pub verification_uri_complete: String,
|
||||||
|
pub expires_in: u64,
|
||||||
|
pub interval: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct AuthorizationToken {
|
||||||
|
pub token_type: String,
|
||||||
|
pub access_token: String,
|
||||||
|
pub refresh_token: String,
|
||||||
|
pub expires_in: u64,
|
||||||
|
pub scope: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn prettify_json_str(json_str: &str) -> Result<String, ()> {
|
||||||
|
let Ok(json) = serde_json::from_str::<serde_json::value::Value>(json_str) else {
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
match serde_json::to_string_pretty(&json) {
|
||||||
|
Ok(s) => Ok(s),
|
||||||
|
Err(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/main.rs
Normal file
157
src/main.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// these were for synapse, disabled with MAS
|
||||||
|
// const USERS_ENDPOINT_V3: &str = "/_synapse/admin/v3/users";
|
||||||
|
// const USERS_ENDPOINT_V2: &str = "/_synapse/admin/v2/users";
|
||||||
|
// const REGISTRATION_TOKENS_ENDPOINT: &str = "/_synapse/admin/v1/registration_tokens";
|
||||||
|
// q25W9kalf3EvMvJbLCkxByXym7OukM1F
|
||||||
|
|
||||||
|
//01KJZ6KYHQRRPMG3M49CZSBTM4Z
|
||||||
|
//01KJZ4RN8HAXYMHBC7F7H608EP
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
pub mod json_types;
|
||||||
|
|
||||||
|
use json_types::*;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use clap::{CommandFactory, Parser, Subcommand};
|
||||||
|
use colored::Colorize;
|
||||||
|
use enum_iterator::Sequence;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
// google oauth
|
||||||
|
// 754241279547-kk48of2t6eu5vol85hn6op3ofpp3em76.apps.googleusercontent.com
|
||||||
|
|
||||||
|
const MATRIX_SERVER_BASE_URL: &str = "https://matrix.supersaturn.space";
|
||||||
|
const MAS_SERVER_BASE_URL: &str = "https://mas.supersaturn.space";
|
||||||
|
|
||||||
|
const CLIENT_SECRET: &str = "asdjfja2392ijf23923ndflkas01812j312k3j_18127";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
//curl --header "Authorization: Bearer syt_anA_YRbjQUlvKVDuAfIDIdPW_0k2VVC" -X GET http://127.0.0.1:8008/_synapse/admin/v2/users/@jp:matrix.supersaturn.space
|
||||||
|
// let access_token = "Bearer mat_zA80YL1OafucuRGgdZSPIILCSquto2_nT9b73";
|
||||||
|
print!("\x1B[2J\x1B[2;5H");
|
||||||
|
println!("{}", "Welcome to auth tools CLI".bright_cyan());
|
||||||
|
println!("{} {MATRIX_SERVER_BASE_URL}\n\n", "Homeserver:".green());
|
||||||
|
print_menu();
|
||||||
|
|
||||||
|
let mut app = App::new(MATRIX_SERVER_BASE_URL, MAS_SERVER_BASE_URL);
|
||||||
|
app.get_auth_metadata();
|
||||||
|
app.register_client();
|
||||||
|
|
||||||
|
app.print_status();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let input = readline().unwrap();
|
||||||
|
let input = input.trim();
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(args) = shlex::split(input) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let cli = match Cli::try_parse_from(args) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", e.to_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Commands::GetAuthMetadata => app.get_auth_metadata(),
|
||||||
|
Commands::RegisterClient => app.register_client(),
|
||||||
|
Commands::AuthorizeDevice => app.authorize_device(),
|
||||||
|
Commands::ShowOauthLinks => app.show_oath_links(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// todo!("nope");
|
||||||
|
|
||||||
|
// let req = client
|
||||||
|
// .post(format!(
|
||||||
|
// "{base_url}{USERS_ENDPT}/01KJXZRB1FSNNQ22DDC0368V9G/deactivate"
|
||||||
|
// ))
|
||||||
|
// .header("Accept", "application/json")
|
||||||
|
// .header("Authorization", access_token)
|
||||||
|
// .header("Content-Type", "application/json")
|
||||||
|
// .body(include_str!("../deactivate.json"));
|
||||||
|
// let req = client
|
||||||
|
// .post(format!("{base_url}{REGISTRATION_TOKENS_ENDPOINT}/new"))
|
||||||
|
// .header("Authorization", format!("Bearer {access_token}"))
|
||||||
|
// .body("{}");
|
||||||
|
// let req = client.get(format!("{base_url}{USERS_ENDPOINT_V3}")).header(
|
||||||
|
// "Authorization",
|
||||||
|
// "Bearer syt_anA_YRbjQUlvKVDuAfIDIdPW_0k2VVC",
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let req = client
|
||||||
|
// .put(format!(
|
||||||
|
// "{base_url}{USERS_ENDPOINT_V2}/@afriend:supersaturn.space"
|
||||||
|
// ))
|
||||||
|
// .header("Authorization", "))
|
||||||
|
// .body(include_str!("../postbody.json"));
|
||||||
|
|
||||||
|
// println!("{:?}", req);
|
||||||
|
|
||||||
|
// let req = req.send().unwrap();
|
||||||
|
|
||||||
|
// match req.status() {
|
||||||
|
// StatusCode::OK | StatusCode::NO_CONTENT | StatusCode::CREATED => (),
|
||||||
|
// StatusCode::UNAUTHORIZED => {
|
||||||
|
// if !req.text().unwrap().contains("token expired") {
|
||||||
|
// panic!("Not authorized: {}", req.text().unwrap());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let req = client.get(format!("{MAS_SERVER_BASE_URL}{TOKEN_ENDPT}"));
|
||||||
|
// }
|
||||||
|
// sc => panic!("Bad response?\n{}\n{:?}", sc, req.text()),
|
||||||
|
// };
|
||||||
|
// let req_txt = req.text().unwrap();
|
||||||
|
|
||||||
|
// // println!("{}", &req_txt);
|
||||||
|
|
||||||
|
// let txt = prettify_json_str(&req_txt);
|
||||||
|
// println!("Blocking req status\n\n{}\n\n", txt.unwrap());
|
||||||
|
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const MENU_HELP_TEMPLATE: &str = "\
|
||||||
|
{about-with-newline}\n\
|
||||||
|
{all-args}{after-help}\
|
||||||
|
";
|
||||||
|
|
||||||
|
fn print_menu() {
|
||||||
|
let mut cli = Cli::command().help_template(MENU_HELP_TEMPLATE);
|
||||||
|
|
||||||
|
cli.print_long_help().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readline() -> Result<String, String> {
|
||||||
|
write!(std::io::stdout(), "> ").map_err(|e| e.to_string())?;
|
||||||
|
std::io::stdout().flush().map_err(|e| e.to_string())?;
|
||||||
|
let mut buffer = String::new();
|
||||||
|
std::io::stdin()
|
||||||
|
.read_line(&mut buffer)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Parser)]
|
||||||
|
#[command(multicall = true)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand, PartialEq, Eq, Sequence)]
|
||||||
|
enum Commands {
|
||||||
|
GetAuthMetadata,
|
||||||
|
RegisterClient,
|
||||||
|
AuthorizeDevice,
|
||||||
|
ShowOauthLinks,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user