feat: Implement models and meta routes. Add empty files for routes still to be implemented.

This commit is contained in:
IAmTomahawkx
2025-07-02 18:43:23 -07:00
parent f8eb324543
commit ff36d71ad5
100 changed files with 718 additions and 156 deletions

View File

@@ -0,0 +1 @@
// This should allow resetting of all factors or individual ones.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
//close/reopen a case

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,2 @@
mod actions;
mod fetch;

View File

@@ -0,0 +1,2 @@
mod actions;
mod fetch;

View File

@@ -0,0 +1,38 @@
use iso8601_timestamp::{Duration, Timestamp};
use revolt_database::{AdminMachineToken, AdminToken, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::user_has_permission;
/// Create a token for your account. Must have the CreateTokens permission
#[openapi(tag = "Admin")]
#[post("/tokens", data = "<body>")]
pub async fn admin_create_token(
db: &State<Database>,
auth: AdminMachineToken,
body: Json<v0::AdminTokenCreate>,
) -> Result<Json<v0::AdminToken>> {
if !user_has_permission(
&auth.on_behalf_of,
v0::AdminUserPermissionFlags::CreateTokens,
) {
return Err(create_error!(NotFound));
}
if body.expiry > Timestamp::now_utc() + Duration::days(30) {
return Err(create_error!(FailedValidation {
error: "The expiry is more than 30 days away.".to_string()
}));
}
let token = AdminToken::new(&auth.on_behalf_of.id, body.expiry);
db.admin_token_create(token.clone()).await?;
Ok(Json(v0::AdminToken {
id: token.id,
user_id: token.user_id,
token: token.token,
expiry: body.expiry,
}))
}

View File

@@ -0,0 +1,26 @@
use revolt_database::{AdminAuthorization, AdminUser, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an admin user. Requires ManageAdminUsers flag.
#[openapi(tag = "Admin")]
#[post("/users", data = "<body>")]
pub async fn admin_create_user(
db: &State<Database>,
auth: AdminAuthorization,
body: Json<v0::AdminUserCreate>,
) -> Result<Json<v0::AdminUser>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) {
return Err(create_error!(NotFound));
}
// TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want.
// But this is built with the assumption that people with ManageAdminUsers are already privileged.
let user = AdminUser::new(&body.email, &body.platform_user_id, body.permissions);
db.admin_user_insert(user.clone()).await?;
Ok(Json(user.into()))
}

View File

@@ -0,0 +1,27 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an admin user. Requires ManageAdminUsers flag.
#[openapi(tag = "Admin")]
#[patch("/users/<target>", data = "<body>")]
pub async fn admin_edit_user(
db: &State<Database>,
auth: AdminAuthorization,
target: Reference,
body: Json<v0::AdminUserEdit>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ManageAdminUsers) {
return Err(create_error!(NotFound));
}
// TODO: technically there's a privilege escalation here since anyone with the manageAdminUsers permission can assign whatever permissions they want.
// But this is built with the assumption that people with ManageAdminUsers are already privileged.
db.admin_user_update(&target.id, body.0.into()).await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,29 @@
use revolt_database::{AdminAuthorization, Database};
use revolt_models::v0::AdminUser;
use revolt_result::Result;
use rocket::{serde::json::Json, State};
/// Get a list of admin users. Any active user may use this endpoint.
/// Typically the client should cache this data.
#[openapi(tag = "Admin")]
#[get("/users")]
pub async fn admin_fetch_users(
db: &State<Database>,
auth: AdminAuthorization,
) -> Result<Json<Vec<AdminUser>>> {
let users = db.admin_user_list().await?;
let userids: Vec<String> = users.iter().map(|u| u.platform_user_id.clone()).collect();
let revolt_users = db.fetch_users(&userids).await?;
let mut resp = vec![];
for admin_user in users {
let mut user = AdminUser::from(admin_user);
user.revolt_user = match revolt_users.iter().find(|ru| ru.id == user.id) {
Some(some_user) => Some(some_user.clone().into_self(false).await),
None => None,
};
resp.push(user);
}
Ok(Json(resp))
}

View File

@@ -0,0 +1,5 @@
pub mod create_token;
pub mod create_user;
pub mod edit_user;
pub mod fetch_users;
pub mod revoke_token;

View File

@@ -0,0 +1,39 @@
use revolt_database::{util::reference::Reference, AdminMachineToken, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::user_has_permission;
/// Revoke a token. Must be your own, or you must have the ManageAdminUsers permission to revoke other's tokens.
/// You must have the CreateTokens permission.
#[openapi(tag = "Admin")]
#[delete("/tokens/<token>")]
pub async fn admin_revoke_token(
db: &State<Database>,
auth: AdminMachineToken,
token: Reference,
) -> Result<EmptyResponse> {
if !user_has_permission(
&auth.on_behalf_of,
v0::AdminUserPermissionFlags::CreateTokens,
) {
return Err(create_error!(NotFound));
}
let token = token.as_admin_token(db).await?;
// only own user and those with ManageAdminUsers can revoke a token
if token.user_id != auth.on_behalf_of.id
&& !user_has_permission(
&auth.on_behalf_of,
v0::AdminUserPermissionFlags::ManageAdminUsers,
)
{
return Err(create_error!(NotFound));
}
db.admin_token_revoke(&token.id).await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,26 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod accounts;
mod cases;
mod meta;
mod reports;
mod roles;
mod search;
mod users;
mod object_edit_note;
mod object_get_note;
mod util;
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
object_edit_note::admin_object_edit_note,
object_get_note::admin_object_get_note,
meta::create_user::admin_create_user,
meta::edit_user::admin_edit_user,
meta::fetch_users::admin_fetch_users,
meta::create_token::admin_create_token,
meta::revoke_token::admin_revoke_token
]
}

View File

@@ -0,0 +1,28 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, AdminObjectNote, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use rocket_empty::EmptyResponse;
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an object note. Requires ObjectNotes permission.
#[openapi(tag = "Admin")]
#[post("/notes/<object>", data = "<body>")]
pub async fn admin_object_edit_note(
db: &State<Database>,
auth: AdminAuthorization,
object: Reference,
body: Json<v0::AdminObjectNoteEdit>,
) -> Result<EmptyResponse> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) {
return Err(create_error!(NotFound));
}
// Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.).
// Hence it being tied to its own permission
db.admin_note_update(AdminObjectNote::new(&object.id, &user.id, &body.content))
.await?;
Ok(EmptyResponse)
}

View File

@@ -0,0 +1,24 @@
use revolt_database::{util::reference::Reference, AdminAuthorization, Database};
use revolt_models::v0::{self};
use revolt_result::{create_error, Result};
use rocket::{serde::json::Json, State};
use crate::routes::admin::util::{flatten_authorized_user, user_has_permission};
/// Edit an object note. Requires ObjectNotes permission.
#[openapi(tag = "Admin")]
#[get("/notes/<object>")]
pub async fn admin_object_get_note(
db: &State<Database>,
auth: AdminAuthorization,
object: Reference,
) -> Result<Json<v0::AdminObjectNote>> {
let user = flatten_authorized_user(&auth);
if !user_has_permission(user, v0::AdminUserPermissionFlags::ObjectNotes) {
return Err(create_error!(NotFound));
}
// Unfortunately due to how our system works, we can't limit users to editing objects they have permissions for (eg users, servers, etc.).
// Hence it being tied to its own permission
Ok(Json(db.admin_note_fetch(&object.id).await?.into()))
}

View File

@@ -0,0 +1,2 @@
mod actions;
mod fetch;

View File

@@ -0,0 +1,3 @@
mod role_create;
mod role_delete;
mod role_edit;

View File

@@ -0,0 +1 @@
mod search_everything;

View File

@@ -0,0 +1,2 @@
mod actions;
mod fetch;

View File

@@ -0,0 +1,2 @@
mod actions;
mod fetch;

View File

@@ -0,0 +1,15 @@
use revolt_database::{AdminAuthorization, AdminUser, AdminUserPermissionFlagsValue};
use revolt_models::v0::AdminUserPermissionFlags;
pub fn user_has_permission(user: &AdminUser, permission: AdminUserPermissionFlags) -> bool {
let flags = AdminUserPermissionFlagsValue(user.permissions);
flags.has(permission)
}
pub fn flatten_authorized_user<'a>(auth: &'a AdminAuthorization) -> &'a AdminUser {
match auth {
AdminAuthorization::AdminUser(admin_user) => &admin_user,
AdminAuthorization::AdminMachine(admin_machine_token) => &admin_machine_token.on_behalf_of,
}
}

View File

@@ -4,6 +4,7 @@ pub use rocket::http::Status;
pub use rocket::response::Redirect;
use rocket::{Build, Rocket};
mod admin;
mod bots;
mod channels;
mod customisation;
@@ -21,89 +22,79 @@ mod webhooks;
pub fn mount(config: Settings, mut rocket: Rocket<Build>) -> Rocket<Build> {
let settings = OpenApiSettings::default();
if config.features.webhooks_enabled {
mount_endpoints_and_merged_docs! {
rocket, "/".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/policy" => policy::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/policy" => policy::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
let mut openapi_list: Vec<(_, revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi)> =
Vec::new();
let routes = vec![
("/", (vec![], custom_openapi_spec()), true, true),
("/", openapi_get_routes_spec![root::root], true, true),
("/users", users::routes(), true, true),
("/bots", bots::routes(), true, true),
("/channels", channels::routes(), true, true),
("/servers", servers::routes(), true, true),
("/invites", invites::routes(), true, true),
("/custom", customisation::routes(), true, true),
("/safety", safety::routes(), true, true),
(
"/auth/mfa",
rocket_authifier::routes::mfa::routes(),
true,
true,
),
("/onboard", onboard::routes(), true, true),
("/policy", policy::routes(), true, true),
("/push", push::routes(), true, true),
("/sync", sync::routes(), true, true),
(
"/auth/account",
rocket_authifier::routes::account::routes(),
true,
true,
),
(
"/auth/session",
rocket_authifier::routes::session::routes(),
true,
true,
),
(
"/admin",
admin::routes(),
config.features.admin_api_enabled,
config.features.admin_api_show_spec,
),
(
"/webhooks",
webhooks::routes(),
config.features.webhooks_enabled,
true,
),
];
for (base, (routes, openapi), enabled, show_spec) in routes {
if !enabled {
continue;
}
rocket = rocket.mount(base, routes);
if show_spec {
openapi_list.push((base, openapi));
}
}
if config.features.webhooks_enabled {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
let openapi_docs =
match revolt_rocket_okapi::revolt_okapi::merge::marge_spec_list(&openapi_list) {
Ok(docs) => docs,
Err(err) => panic!("Could not merge OpenAPI spec: {}", err),
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
}
rocket = rocket.mount(
"/",
vec![revolt_rocket_okapi::get_openapi_route(
openapi_docs,
&settings,
)],
);
rocket
}
@@ -238,11 +229,6 @@ fn custom_openapi_spec() -> OpenApi {
description: Some("Local Revolt Environment".to_owned()),
..Default::default()
},
Server {
url: "http://local.revolt.chat:14702/0.8".to_owned(),
description: Some("Local Revolt Environment (v0.8)".to_owned()),
..Default::default()
},
],
external_docs: Some(ExternalDocs {
url: "https://developers.revolt.chat".to_owned(),
@@ -251,6 +237,13 @@ fn custom_openapi_spec() -> OpenApi {
}),
extensions,
tags: vec![
Tag {
name: "Admin".to_owned(),
description: Some(
"Used when running your own instance to manage and moderate".to_owned(),
),
..Default::default()
},
Tag {
name: "Core".to_owned(),
description: Some(

View File

@@ -123,6 +123,7 @@ pub async fn report_content(
additional_context: data.additional_context,
status: ReportStatus::Created {},
notes: String::new(),
case_id: None,
};
db.insert_report(&report).await?;