From 890ca846d093f5cd03674d401bee2baa359d2094 Mon Sep 17 00:00:00 2001 From: naskya <m@naskya.net> Date: Thu, 30 May 2024 07:03:51 +0900 Subject: [PATCH] chore (backend-rs): documents, organize exports, typo fixes --- docs/activitypub-extensions.md | 2 +- packages/backend-rs/index.d.ts | 96 +++++++++++++++++-- packages/backend-rs/src/config/constant.rs | 17 ++-- packages/backend-rs/src/config/mod.rs | 2 + packages/backend-rs/src/database/cache.rs | 2 + packages/backend-rs/src/database/mod.rs | 5 +- .../backend-rs/src/database/postgresql.rs | 7 +- packages/backend-rs/src/database/redis.rs | 11 ++- packages/backend-rs/src/federation/mod.rs | 2 + .../src/federation/nodeinfo/fetch.rs | 3 + .../src/federation/nodeinfo/generate.rs | 4 + .../backend-rs/src/federation/nodeinfo/mod.rs | 2 + .../src/federation/nodeinfo/schema.rs | 6 +- packages/backend-rs/src/init/greet.rs | 1 + packages/backend-rs/src/init/log.rs | 1 + packages/backend-rs/src/init/mod.rs | 2 + packages/backend-rs/src/init/system_info.rs | 13 ++- packages/backend-rs/src/lib.rs | 8 -- .../backend-rs/src/misc/check_server_block.rs | 65 ++++++++++--- .../backend-rs/src/misc/check_word_mute.rs | 14 +++ packages/backend-rs/src/misc/convert_host.rs | 42 +++----- packages/backend-rs/src/misc/emoji.rs | 2 + packages/backend-rs/src/misc/escape_sql.rs | 2 + .../src/misc/format_milliseconds.rs | 2 +- .../backend-rs/src/misc/get_note_all_texts.rs | 4 +- .../backend-rs/src/misc/latest_version.rs | 3 + packages/backend-rs/src/misc/mod.rs | 2 + packages/backend-rs/src/misc/nyaify.rs | 19 ++++ packages/backend-rs/src/misc/password.rs | 9 +- packages/backend-rs/src/misc/reaction.rs | 3 +- .../misc/remove_old_attestation_challenges.rs | 2 +- packages/backend-rs/src/misc/system_info.rs | 2 + packages/backend-rs/src/model/mod.rs | 2 + packages/backend-rs/src/service/mod.rs | 2 + packages/backend-rs/src/util/http_client.rs | 2 + packages/backend-rs/src/util/id.rs | 2 + packages/backend-rs/src/util/mod.rs | 5 +- packages/backend-rs/src/util/random.rs | 4 +- .../backend/src/server/api/private/signin.ts | 1 + packages/macro-rs/src/lib.rs | 8 +- 40 files changed, 284 insertions(+), 97 deletions(-) diff --git a/docs/activitypub-extensions.md b/docs/activitypub-extensions.md index 4d5faeff80..7c1e35ac91 100644 --- a/docs/activitypub-extensions.md +++ b/docs/activitypub-extensions.md @@ -5,6 +5,6 @@ These are the extensions to ActivityPub that Firefish implements. This page uses ## speakAsCat - Compact IRI: `firefish:speakAsCat` -- Canonical IRI: `https://firefish.dev/ns#speakascat` +- Canonical IRI: `https://firefish.dev/ns#speakAsCat` Used on actors to indicate that they not only identify as a cat, but also want to have their text be transformed to speak like one, expressed as a boolean value. If this property is set to true, displaying the actor’s posts will make them speak with “nya” instead of “na” and other cat-related text mannerisms. Used in combination with [misskey:isCat](https://misskey-hub.net/ns/#iscat). diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 30e59f5353..5060e38bdb 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -11,8 +11,9 @@ export const USER_ONLINE_THRESHOLD: number export const USER_ACTIVE_THRESHOLD: number /** * List of file types allowed to be viewed directly in the browser + * * Anything not included here will be responded as application/octet-stream - * SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly + * SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly) * * <https://github.com/sindresorhus/file-type/blob/main/supported.js> * * <https://github.com/sindresorhus/file-type/blob/main/core.js> * * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers> @@ -207,6 +208,7 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string +/** Fetches and returns the NodeInfo of a remote server. */ export function fetchNodeinfo(host: string): Promise<Nodeinfo> export function nodeinfo_2_1(): Promise<any> export function nodeinfo_2_0(): Promise<any> @@ -307,31 +309,86 @@ export interface Users { activeHalfyear: number | null activeMonth: number | null } +/** Prints the greeting message and the Firefish version to stdout. */ export function greet(): void +/** Initializes the [tracing] logger. */ export function initializeRustLogger(): void +/** Prints the server hardware information as the server info log. */ export function showServerInfo(): void /** * Checks if a server is blocked. * - * ## Argument + * # Argument * `host` - punycoded instance host + * + * # Example + * ```no_run + * # use backend_rs::misc::check_server_block::is_blocked_server; + * # async fn f() -> Result<(), Box<dyn std::error::Error>> { + * assert_eq!(true, is_blocked_server("blocked.com").await?); + * assert_eq!(false, is_blocked_server("not-blocked.com").await?); + * assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?); + * assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?); + * # Ok(()) + * # } + * ``` */ export function isBlockedServer(host: string): Promise<boolean> /** * Checks if a server is silenced. * - * ## Argument + * # Argument * `host` - punycoded instance host + * + * # Example + * ```no_run + * # use backend_rs::misc::check_server_block::is_silenced_server; + * # async fn f() -> Result<(), Box<dyn std::error::Error>> { + * assert_eq!(true, is_silenced_server("silenced.com").await?); + * assert_eq!(false, is_silenced_server("not-silenced.com").await?); + * assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?); + * assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?); + * # Ok(()) + * # } + * ``` */ export function isSilencedServer(host: string): Promise<boolean> /** * Checks if a server is allowlisted. * Returns `Ok(true)` if private mode is disabled. * - * ## Argument + * # Argument * `host` - punycoded instance host + * + * # Example + * ```no_run + * # use backend_rs::misc::check_server_block::is_allowed_server; + * # async fn f() -> Result<(), Box<dyn std::error::Error>> { + * assert_eq!(true, is_allowed_server("allowed.com").await?); + * assert_eq!(false, is_allowed_server("not-allowed.com").await?); + * assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?); + * assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?); + * # Ok(()) + * # } + * ``` */ export function isAllowedServer(host: string): Promise<boolean> +/** + * Returns whether `note` should be hard-muted. + * + * More specifically, this function returns `Ok(true)` + * if and only if one or more of these conditions are met: + * + * * the note (text or CW) contains any of the words/patterns + * * the "parent" note(s) (reply, quote) contain any of the words/patterns + * * the alt text of the attached files contains any of the words/patterns + * + * # Arguments + * + * * `note` : [NoteLike] object + * * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition) + * * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions + */ export function checkWordMute(note: NoteLike, mutedWords: Array<string>, mutedPatterns: Array<string>): Promise<boolean> export function getFullApAccount(username: string, host?: string | undefined | null): string export function isSelfHost(host?: string | undefined | null): boolean @@ -339,9 +396,11 @@ export function isSameOrigin(uri: string): boolean export function extractHost(uri: string): string export function toPuny(host: string): string export function isUnicodeEmoji(s: string): boolean +/** Escapes `%` and `\` in the given string. */ export function sqlLikeEscape(src: string): string +/** Returns `true` if `src` does not contain suspicious characters like `%`. */ export function safeForSql(src: string): boolean -/** Convert milliseconds to a human readable string */ +/** Converts milliseconds to a human readable string. */ export function formatMilliseconds(milliseconds: number): string export interface ImageSize { width: number @@ -365,6 +424,7 @@ export interface NoteLikeForGetNoteSummary { export function getNoteSummary(note: NoteLikeForGetNoteSummary): string export function isQuote(note: Note): boolean export function isSafeUrl(url: string): boolean +/** Returns the latest Firefish version. */ export function latestVersion(): Promise<string> export function toMastodonId(firefishId: string): string | null export function fromMastodonId(mastodonId: string): string | null @@ -381,9 +441,31 @@ export interface PugArgs { privateMode: boolean | null } export function metaToPugArgs(meta: Meta): PugArgs +/** + * Converts the given text into the cat language. + * + * refs: + * * <https://misskey-hub.net/ns/#isCat> + * * <https://firefish.dev/ns#speakAsCat> + * + * # Arguments + * + * * `text` : original text + * * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`) + * + * # Example + * + * ``` + * # use backend_rs::misc::nyaify::nyaify; + * assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap."); + * ``` + */ export function nyaify(text: string, lang?: string | undefined | null): string +/** Hashes the given password using [Argon2] algorithm. */ export function hashPassword(password: string): string +/** Checks whether the given password and hash match. */ export function verifyPassword(password: string, hash: string): boolean +/** Returns whether the [bcrypt] algorithm is used for the password hash. */ export function isOldPasswordAlgorithm(hash: string): boolean export interface DecodedReaction { reaction: string @@ -393,7 +475,7 @@ export interface DecodedReaction { export function decodeReaction(reaction: string): DecodedReaction export function countReactions(reactions: Record<string, number>): Record<string, number> export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string> -/** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */ +/** Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago */ export function removeOldAttestationChallenges(): Promise<void> export interface Cpu { model: string @@ -1336,6 +1418,6 @@ export function getTimestamp(id: string): number export function genId(): string /** Generate an ID using a specific datetime */ export function genIdAt(date: Date): string -/** Generate random string based on [thread_rng] and [Alphanumeric]. */ +/** Generates a random string based on [thread_rng] and [Alphanumeric]. */ export function generateSecureRandomString(length: number): string export function generateUserToken(): string diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs index e0e7c7459a..db6e7bed00 100644 --- a/packages/backend-rs/src/config/constant.rs +++ b/packages/backend-rs/src/config/constant.rs @@ -1,24 +1,25 @@ -#[crate::export] +#[crate::ts_export] pub const SECOND: i32 = 1000; -#[crate::export] +#[crate::ts_export] pub const MINUTE: i32 = 60 * SECOND; -#[crate::export] +#[crate::ts_export] pub const HOUR: i32 = 60 * MINUTE; -#[crate::export] +#[crate::ts_export] pub const DAY: i32 = 24 * HOUR; -#[crate::export] +#[crate::ts_export] pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE; -#[crate::export] +#[crate::ts_export] pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY; /// List of file types allowed to be viewed directly in the browser +/// /// Anything not included here will be responded as application/octet-stream -/// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly +/// SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly) /// * <https://github.com/sindresorhus/file-type/blob/main/supported.js> /// * <https://github.com/sindresorhus/file-type/blob/main/core.js> /// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers> -#[crate::export] +#[crate::ts_export] pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [ // Images "image/png", diff --git a/packages/backend-rs/src/config/mod.rs b/packages/backend-rs/src/config/mod.rs index 1bc5eaeb7e..0e8056a894 100644 --- a/packages/backend-rs/src/config/mod.rs +++ b/packages/backend-rs/src/config/mod.rs @@ -1,3 +1,5 @@ +//! Server configurations and environment variables + pub use server::CONFIG; pub mod constant; diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs index 0488e0b0aa..bf675efa38 100644 --- a/packages/backend-rs/src/database/cache.rs +++ b/packages/backend-rs/src/database/cache.rs @@ -1,3 +1,5 @@ +//! Utilities for using Redis cache + use crate::database::{redis_conn, redis_key, RedisConnError}; use redis::{AsyncCommands, RedisError}; use serde::{Deserialize, Serialize}; diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs index e80c3c74a2..24710ca792 100644 --- a/packages/backend-rs/src/database/mod.rs +++ b/packages/backend-rs/src/database/mod.rs @@ -1,11 +1,10 @@ +//! Interfaces for accessing PostgreSQL and Redis + pub use postgresql::db_conn; pub use redis::key as redis_key; pub use redis::redis_conn; pub use redis::RedisConnError; -/// Utilities for using Redis cache pub mod cache; -/// PostgreSQL interface pub mod postgresql; -/// Redis interface pub mod redis; diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs index 13cfccb9c2..ed68227419 100644 --- a/packages/backend-rs/src/database/postgresql.rs +++ b/packages/backend-rs/src/database/postgresql.rs @@ -1,3 +1,5 @@ +//! PostgreSQL interface + use crate::config::CONFIG; use once_cell::sync::OnceCell; use sea_orm::{ConnectOptions, Database, DbConn, DbErr}; @@ -5,7 +7,7 @@ use tracing::log::LevelFilter; static DB_CONN: OnceCell<DbConn> = OnceCell::new(); -async fn init_database() -> Result<&'static DbConn, DbErr> { +async fn init_conn() -> Result<&'static DbConn, DbErr> { let database_uri = format!( "postgres://{}:{}@{}:{}/{}", CONFIG.db.user, @@ -24,10 +26,11 @@ async fn init_database() -> Result<&'static DbConn, DbErr> { Ok(DB_CONN.get_or_init(move || conn)) } +/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities. pub async fn db_conn() -> Result<&'static DbConn, DbErr> { match DB_CONN.get() { Some(conn) => Ok(conn), - None => init_database().await, + None => init_conn().await, } } diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs index 2ef0499bb5..d88d61d514 100644 --- a/packages/backend-rs/src/database/redis.rs +++ b/packages/backend-rs/src/database/redis.rs @@ -1,18 +1,20 @@ +//! Redis interface + use crate::config::CONFIG; use async_trait::async_trait; use bb8::{ManageConnection, Pool, PooledConnection, RunError}; use redis::{aio::MultiplexedConnection, Client, ErrorKind, IntoConnectionInfo, RedisError}; use tokio::sync::OnceCell; -/// A `bb8::ManageConnection` for `redis::Client::get_multiplexed_async_connection`. +/// A [bb8::ManageConnection] for [redis::Client::get_multiplexed_async_connection]. #[derive(Clone, Debug)] pub struct RedisConnectionManager { client: Client, } impl RedisConnectionManager { - /// Create a new `RedisConnectionManager`. - /// See `redis::Client::open` for a description of the parameter types. + /// Creates a new [RedisConnectionManager]. + /// See [redis::Client::open] for a description of the parameter types. pub fn new<T: IntoConnectionInfo>(info: T) -> Result<Self, RedisError> { Ok(Self { client: Client::open(info.into_connection_info()?)?, @@ -85,6 +87,7 @@ pub enum RedisConnError { Bb8Pool(RunError<RedisError>), } +/// Returns an async [redis] connection managed by a [bb8] connection pool. pub async fn redis_conn( ) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> { if !CONN_POOL.initialized() { @@ -103,7 +106,7 @@ pub async fn redis_conn( .map_err(RedisConnError::Bb8Pool) } -/// prefix redis key +/// prefix Redis key #[inline] pub fn key(key: impl ToString) -> String { format!("{}:{}", CONFIG.redis_key_prefix, key.to_string()) diff --git a/packages/backend-rs/src/federation/mod.rs b/packages/backend-rs/src/federation/mod.rs index 4251d784bf..722a91abdf 100644 --- a/packages/backend-rs/src/federation/mod.rs +++ b/packages/backend-rs/src/federation/mod.rs @@ -1,2 +1,4 @@ +//! Services used to federate with other servers + pub mod acct; pub mod nodeinfo; diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs index b68b222626..14ed838912 100644 --- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs +++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs @@ -1,3 +1,5 @@ +//! NodeInfo fetcher + use crate::federation::nodeinfo::schema::*; use crate::util::http_client; use isahc::AsyncReadResponseExt; @@ -83,6 +85,7 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> { // for napi export type Nodeinfo = Nodeinfo20; +/// Fetches and returns the NodeInfo of a remote server. #[crate::export] pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> { tracing::info!("fetching from {}", host); diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs index 804dd3213c..b14c02643f 100644 --- a/packages/backend-rs/src/federation/nodeinfo/generate.rs +++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs @@ -1,3 +1,5 @@ +//! NodeInfo generator + use crate::config::CONFIG; use crate::database::{cache, db_conn}; use crate::federation::nodeinfo::schema::*; @@ -112,6 +114,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> { }) } +/// Returns NodeInfo (version 2.1) of the local server. pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> { const NODEINFO_2_1_CACHE_KEY: &str = "nodeinfo_2_1"; @@ -126,6 +129,7 @@ pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> { } } +/// Returns NodeInfo (version 2.0) of the local server. pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, Error> { Ok(nodeinfo_2_1().await?.into()) } diff --git a/packages/backend-rs/src/federation/nodeinfo/mod.rs b/packages/backend-rs/src/federation/nodeinfo/mod.rs index 4d1eb1fa90..d1e3fc2ed5 100644 --- a/packages/backend-rs/src/federation/nodeinfo/mod.rs +++ b/packages/backend-rs/src/federation/nodeinfo/mod.rs @@ -1,3 +1,5 @@ +//! NodeInfo handler + pub mod fetch; pub mod generate; mod schema; diff --git a/packages/backend-rs/src/federation/nodeinfo/schema.rs b/packages/backend-rs/src/federation/nodeinfo/schema.rs index b56c84ec70..33ab79e2f0 100644 --- a/packages/backend-rs/src/federation/nodeinfo/schema.rs +++ b/packages/backend-rs/src/federation/nodeinfo/schema.rs @@ -1,9 +1,11 @@ +//! Schema definitions of NodeInfo version 2.0 and 2.1 + use serde::{Deserialize, Serialize}; use std::collections::HashMap; // TODO: I want to use these macros but they don't work with rmp_serde -// - #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86) -// - #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318) +// * #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86) +// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318) /// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1> #[derive(Deserialize, Serialize, Debug, PartialEq)] diff --git a/packages/backend-rs/src/init/greet.rs b/packages/backend-rs/src/init/greet.rs index 62854d3721..323548e59c 100644 --- a/packages/backend-rs/src/init/greet.rs +++ b/packages/backend-rs/src/init/greet.rs @@ -11,6 +11,7 @@ const GREETING_MESSAGE: &str = "\ If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish "; +/// Prints the greeting message and the Firefish version to stdout. #[crate::export] pub fn greet() { println!("{}", GREETING_MESSAGE); diff --git a/packages/backend-rs/src/init/log.rs b/packages/backend-rs/src/init/log.rs index 07966b77ad..beb49decdc 100644 --- a/packages/backend-rs/src/init/log.rs +++ b/packages/backend-rs/src/init/log.rs @@ -2,6 +2,7 @@ use crate::config::CONFIG; use tracing::Level; use tracing_subscriber::FmtSubscriber; +/// Initializes the [tracing] logger. #[crate::export(js_name = "initializeRustLogger")] pub fn initialize_logger() { let mut builder = FmtSubscriber::builder(); diff --git a/packages/backend-rs/src/init/mod.rs b/packages/backend-rs/src/init/mod.rs index e76c732184..b84bb27537 100644 --- a/packages/backend-rs/src/init/mod.rs +++ b/packages/backend-rs/src/init/mod.rs @@ -1,3 +1,5 @@ +//! Initializers + pub mod greet; pub mod log; pub mod system_info; diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs index dc5b9b8aa9..138e7486e0 100644 --- a/packages/backend-rs/src/init/system_info.rs +++ b/packages/backend-rs/src/init/system_info.rs @@ -5,10 +5,21 @@ pub type SysinfoPoisonError = PoisonError<MutexGuard<'static, System>>; static SYSTEM_INFO: OnceLock<Mutex<System>> = OnceLock::new(); -pub fn system_info() -> &'static std::sync::Mutex<sysinfo::System> { +/// Gives an access to the shared static [System] object. +/// +/// # Example +/// +/// ``` +/// # use backend_rs::init::system_info::{system_info, SysinfoPoisonError}; +/// let system_info = system_info().lock()?; +/// println!("The number of CPU threads is {}.", system_info.cpus().len()); +/// # Ok::<(), SysinfoPoisonError>(()) +/// ``` +pub fn system_info() -> &'static std::sync::Mutex<System> { SYSTEM_INFO.get_or_init(|| Mutex::new(System::new_all())) } +/// Prints the server hardware information as the server info log. #[crate::export] pub fn show_server_info() -> Result<(), SysinfoPoisonError> { let system_info = system_info().lock()?; diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs index ed601b9f51..d62ab792d6 100644 --- a/packages/backend-rs/src/lib.rs +++ b/packages/backend-rs/src/lib.rs @@ -1,18 +1,10 @@ use macro_rs::{export, ts_export}; -/// Server configurations and environment variables pub mod config; -/// Interfaces for accessing PostgreSQL and Redis pub mod database; -/// Services used to federate with other servers pub mod federation; -/// Initializers pub mod init; -/// Miscellaneous utilities pub mod misc; -/// Database structure, auto-generated by [sea_orm] pub mod model; -/// Services provided for local users pub mod service; -/// Basic utilities such as ID generator and HTTP client pub mod util; diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs index bb7f40078a..fe0c118521 100644 --- a/packages/backend-rs/src/misc/check_server_block.rs +++ b/packages/backend-rs/src/misc/check_server_block.rs @@ -1,13 +1,26 @@ -use crate::misc::meta::fetch_meta; -use sea_orm::DbErr; +//! This module is used in the TypeScript backend only. +// We may want to (re)implement these functions in the `federation` module +// in a Rusty way (e.g., traits of server type) if needed. /// Checks if a server is blocked. /// -/// ## Argument +/// # Argument /// `host` - punycoded instance host -#[crate::export] -pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> { - Ok(fetch_meta(true) +/// +/// # Example +/// ```no_run +/// # use backend_rs::misc::check_server_block::is_blocked_server; +/// # async fn f() -> Result<(), Box<dyn std::error::Error>> { +/// assert_eq!(true, is_blocked_server("blocked.com").await?); +/// assert_eq!(false, is_blocked_server("not-blocked.com").await?); +/// assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?); +/// assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?); +/// # Ok(()) +/// # } +/// ``` +#[crate::ts_export] +pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> { + Ok(crate::misc::meta::fetch_meta(true) .await? .blocked_hosts .iter() @@ -18,11 +31,23 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> { /// Checks if a server is silenced. /// -/// ## Argument +/// # Argument /// `host` - punycoded instance host -#[crate::export] -pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> { - Ok(fetch_meta(true) +/// +/// # Example +/// ```no_run +/// # use backend_rs::misc::check_server_block::is_silenced_server; +/// # async fn f() -> Result<(), Box<dyn std::error::Error>> { +/// assert_eq!(true, is_silenced_server("silenced.com").await?); +/// assert_eq!(false, is_silenced_server("not-silenced.com").await?); +/// assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?); +/// assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?); +/// # Ok(()) +/// # } +/// ``` +#[crate::ts_export] +pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> { + Ok(crate::misc::meta::fetch_meta(true) .await? .silenced_hosts .iter() @@ -34,11 +59,23 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> { /// Checks if a server is allowlisted. /// Returns `Ok(true)` if private mode is disabled. /// -/// ## Argument +/// # Argument /// `host` - punycoded instance host -#[crate::export] -pub async fn is_allowed_server(host: &str) -> Result<bool, DbErr> { - let meta = fetch_meta(true).await?; +/// +/// # Example +/// ```no_run +/// # use backend_rs::misc::check_server_block::is_allowed_server; +/// # async fn f() -> Result<(), Box<dyn std::error::Error>> { +/// assert_eq!(true, is_allowed_server("allowed.com").await?); +/// assert_eq!(false, is_allowed_server("not-allowed.com").await?); +/// assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?); +/// assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?); +/// # Ok(()) +/// # } +/// ``` +#[crate::ts_export] +pub async fn is_allowed_server(host: &str) -> Result<bool, sea_orm::DbErr> { + let meta = crate::misc::meta::fetch_meta(true).await?; if !meta.private_mode.unwrap_or(false) { return Ok(true); diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs index aba792baa5..4109cb9a36 100644 --- a/packages/backend-rs/src/misc/check_word_mute.rs +++ b/packages/backend-rs/src/misc/check_word_mute.rs @@ -26,6 +26,20 @@ fn check_word_mute_impl( }) } +/// Returns whether `note` should be hard-muted. +/// +/// More specifically, this function returns `Ok(true)` +/// if and only if one or more of these conditions are met: +/// +/// * the note (text or CW) contains any of the words/patterns +/// * the "parent" note(s) (reply, quote) contain any of the words/patterns +/// * the alt text of the attached files contains any of the words/patterns +/// +/// # Arguments +/// +/// * `note` : [NoteLike] object +/// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition) +/// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions #[crate::export] pub async fn check_word_mute( note: NoteLike, diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs index 2a88ce7ccd..c086285af1 100644 --- a/packages/backend-rs/src/misc/convert_host.rs +++ b/packages/backend-rs/src/misc/convert_host.rs @@ -1,4 +1,6 @@ -use crate::config::CONFIG; +//! This module is used in the TypeScript backend only. +// We may want to (re)implement these functions in the `federation` module +// in a Rusty way (e.g., traits of actor type) if needed. #[derive(thiserror::Error, Debug)] pub enum Error { @@ -10,28 +12,28 @@ pub enum Error { NoHostname, } -#[crate::export] +#[crate::ts_export] pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String, Error> { Ok(match host { Some(host) => format!("{}@{}", username, to_puny(host)?), - None => format!("{}@{}", username, extract_host(&CONFIG.url)?), + None => format!("{}@{}", username, extract_host(&crate::config::CONFIG.url)?), }) } -#[crate::export] +#[crate::ts_export] pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> { Ok(match host { - Some(host) => extract_host(&CONFIG.url)? == to_puny(host)?, + Some(host) => extract_host(&crate::config::CONFIG.url)? == to_puny(host)?, None => true, }) } -#[crate::export] +#[crate::ts_export] pub fn is_same_origin(uri: &str) -> Result<bool, Error> { - Ok(url::Url::parse(uri)?.origin().ascii_serialization() == CONFIG.url) + Ok(url::Url::parse(uri)?.origin().ascii_serialization() == crate::config::CONFIG.url) } -#[crate::export] +#[crate::ts_export] pub fn extract_host(uri: &str) -> Result<String, Error> { url::Url::parse(uri)? .host_str() @@ -39,29 +41,7 @@ pub fn extract_host(uri: &str) -> Result<String, Error> { .and_then(|v| Ok(to_puny(v)?)) } -#[crate::export] +#[crate::ts_export] pub fn to_puny(host: &str) -> Result<String, idna::Errors> { idna::domain_to_ascii(host) } - -#[cfg(test)] -mod unit_test { - use super::{extract_host, to_puny}; - use pretty_assertions::assert_eq; - - #[test] - fn extract_host_test() { - assert_eq!( - extract_host("https://firefish.dev/firefish/firefish.git").unwrap(), - "firefish.dev" - ); - } - - #[test] - fn to_puny_test() { - assert_eq!( - to_puny("何もかも.owari.shop").unwrap(), - "xn--u8jyfb5762a.owari.shop" - ); - } -} diff --git a/packages/backend-rs/src/misc/emoji.rs b/packages/backend-rs/src/misc/emoji.rs index b244dbd8e1..a19fc6229d 100644 --- a/packages/backend-rs/src/misc/emoji.rs +++ b/packages/backend-rs/src/misc/emoji.rs @@ -1,3 +1,5 @@ +//! This module is used in the TypeScript backend only. + #[crate::ts_export] pub fn is_unicode_emoji(s: &str) -> bool { emojis::get(s).is_some() diff --git a/packages/backend-rs/src/misc/escape_sql.rs b/packages/backend-rs/src/misc/escape_sql.rs index c575e088ce..31b8b964ab 100644 --- a/packages/backend-rs/src/misc/escape_sql.rs +++ b/packages/backend-rs/src/misc/escape_sql.rs @@ -1,8 +1,10 @@ +/// Escapes `%` and `\` in the given string. #[crate::export] pub fn sql_like_escape(src: &str) -> String { src.replace('%', r"\%").replace('_', r"\_") } +/// Returns `true` if `src` does not contain suspicious characters like `%`. #[crate::export] pub fn safe_for_sql(src: &str) -> bool { !src.contains([ diff --git a/packages/backend-rs/src/misc/format_milliseconds.rs b/packages/backend-rs/src/misc/format_milliseconds.rs index dfa8df6f62..20c67a773a 100644 --- a/packages/backend-rs/src/misc/format_milliseconds.rs +++ b/packages/backend-rs/src/misc/format_milliseconds.rs @@ -1,4 +1,4 @@ -/// Convert milliseconds to a human readable string +/// Converts milliseconds to a human readable string. #[crate::export] pub fn format_milliseconds(milliseconds: u32) -> String { let mut seconds = milliseconds / 1000; diff --git a/packages/backend-rs/src/misc/get_note_all_texts.rs b/packages/backend-rs/src/misc/get_note_all_texts.rs index 6c926ea374..8199085a53 100644 --- a/packages/backend-rs/src/misc/get_note_all_texts.rs +++ b/packages/backend-rs/src/misc/get_note_all_texts.rs @@ -18,8 +18,8 @@ pub struct NoteLike { /// /// ## Arguments /// -/// * `note` - [NoteLike] object -/// * `include_parent` - whether to take the reply-to post and quoted post into account +/// * `note` : [NoteLike] object +/// * `include_parent` : whether to take the reply-to post and quoted post into account pub async fn all_texts(note: NoteLike, include_parent: bool) -> Result<Vec<String>, DbErr> { let db = db_conn().await?; diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs index 544f922e53..8c361aa5bf 100644 --- a/packages/backend-rs/src/misc/latest_version.rs +++ b/packages/backend-rs/src/misc/latest_version.rs @@ -1,3 +1,5 @@ +//! Fetch latest Firefish version from the Firefish repository + use crate::database::cache; use crate::util::http_client; use isahc::ReadResponseExt; @@ -43,6 +45,7 @@ async fn get_latest_version() -> Result<String, Error> { Ok(res_parsed.version) } +/// Returns the latest Firefish version. #[crate::export] pub async fn latest_version() -> Result<String, Error> { let version: Option<String> = diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 3aefacc48c..23782b11a0 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -1,3 +1,5 @@ +//! Miscellaneous utilities + pub mod check_server_block; pub mod check_word_mute; pub mod convert_host; diff --git a/packages/backend-rs/src/misc/nyaify.rs b/packages/backend-rs/src/misc/nyaify.rs index 94f1615d67..e9c5f414ca 100644 --- a/packages/backend-rs/src/misc/nyaify.rs +++ b/packages/backend-rs/src/misc/nyaify.rs @@ -1,6 +1,25 @@ +//! Cat language converter + use once_cell::sync::Lazy; use regex::{Captures, Regex}; +/// Converts the given text into the cat language. +/// +/// refs: +/// * <https://misskey-hub.net/ns/#isCat> +/// * <https://firefish.dev/ns#speakAsCat> +/// +/// # Arguments +/// +/// * `text` : original text +/// * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`) +/// +/// # Example +/// +/// ``` +/// # use backend_rs::misc::nyaify::nyaify; +/// assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap."); +/// ``` #[crate::export] pub fn nyaify(text: &str, lang: Option<&str>) -> String { let mut to_return = text.to_owned(); diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs index ecef7f6da8..c91de82562 100644 --- a/packages/backend-rs/src/misc/password.rs +++ b/packages/backend-rs/src/misc/password.rs @@ -1,9 +1,12 @@ +//! Utilities for password hash generation and verification + use argon2::{ password_hash, password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; +/// Hashes the given password using [Argon2] algorithm. #[crate::export] pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> { let salt = SaltString::generate(&mut OsRng); @@ -13,7 +16,7 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Er } #[derive(thiserror::Error, Debug)] -pub enum VerifyError { +pub enum Error { #[error("An error occured while bcrypt verification: {0}")] Bcrypt(#[from] bcrypt::BcryptError), #[error("Invalid argon2 password hash: {0}")] @@ -22,8 +25,9 @@ pub enum VerifyError { Argon2(#[from] argon2::Error), } +/// Checks whether the given password and hash match. #[crate::export] -pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError> { +pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> { if is_old_password_algorithm(hash) { Ok(bcrypt::verify(password, hash)?) } else { @@ -34,6 +38,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError> } } +/// Returns whether the [bcrypt] algorithm is used for the password hash. #[inline] #[crate::export] pub fn is_old_password_algorithm(hash: &str) -> bool { diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs index d080649fb7..f07d4761cb 100644 --- a/packages/backend-rs/src/misc/reaction.rs +++ b/packages/backend-rs/src/misc/reaction.rs @@ -1,5 +1,4 @@ use crate::database::db_conn; -use crate::misc::convert_host::to_puny; use crate::misc::meta::fetch_meta; use crate::model::entity::emoji; use once_cell::sync::Lazy; @@ -87,7 +86,7 @@ pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Resul if let Some(host) = host { // remote emoji - let ascii_host = to_puny(host)?; + let ascii_host = idna::domain_to_ascii(host)?; // TODO: Does SeaORM have the `exists` method? if emoji::Entity::find() diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs index 75b8fcd7e3..58c0ea8f3a 100644 --- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs +++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs @@ -5,7 +5,7 @@ use crate::model::entity::attestation_challenge; use chrono::{Duration, Utc}; use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter}; -/// Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago +/// Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago #[crate::export] pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> { let res = attestation_challenge::Entity::delete_many() diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs index ec657a2f70..95bd0dc490 100644 --- a/packages/backend-rs/src/misc/system_info.rs +++ b/packages/backend-rs/src/misc/system_info.rs @@ -1,3 +1,5 @@ +//! Utilities to check hardware information such as cpu, memory, storage usage + use crate::init::system_info::{system_info, SysinfoPoisonError}; use sysinfo::{Disks, MemoryRefreshKind}; diff --git a/packages/backend-rs/src/model/mod.rs b/packages/backend-rs/src/model/mod.rs index e8c3d6a4c6..0d8646a5f6 100644 --- a/packages/backend-rs/src/model/mod.rs +++ b/packages/backend-rs/src/model/mod.rs @@ -1 +1,3 @@ +//! Database structure, auto-generated by [sea_orm] + pub mod entity; diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs index abbd7fd7c4..eedc5751b4 100644 --- a/packages/backend-rs/src/service/mod.rs +++ b/packages/backend-rs/src/service/mod.rs @@ -1,3 +1,5 @@ +//! Services provided for local users + pub mod antenna; pub mod note; pub mod push_notification; diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs index c2c917964d..3e861966d2 100644 --- a/packages/backend-rs/src/util/http_client.rs +++ b/packages/backend-rs/src/util/http_client.rs @@ -1,3 +1,5 @@ +//! Shared [isahc] HTTP client + use crate::config::CONFIG; use isahc::{config::*, HttpClient}; use once_cell::sync::OnceCell; diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs index fb0c25b7d3..39e4c34045 100644 --- a/packages/backend-rs/src/util/id.rs +++ b/packages/backend-rs/src/util/id.rs @@ -1,3 +1,5 @@ +//! ID generation utility based on [cuid2] + use crate::config::CONFIG; use basen::BASE36; use chrono::{DateTime, NaiveDateTime, Utc}; diff --git a/packages/backend-rs/src/util/mod.rs b/packages/backend-rs/src/util/mod.rs index 21e9b157a4..761527c4ae 100644 --- a/packages/backend-rs/src/util/mod.rs +++ b/packages/backend-rs/src/util/mod.rs @@ -1,6 +1,5 @@ -/// Shared [isahc] HTTP client +//! Basic utilities such as ID generator and HTTP client + pub mod http_client; -/// ID generation utility based on [cuid2] pub mod id; -/// Secure random string generator pub mod random; diff --git a/packages/backend-rs/src/util/random.rs b/packages/backend-rs/src/util/random.rs index 4251a7c232..c757330f80 100644 --- a/packages/backend-rs/src/util/random.rs +++ b/packages/backend-rs/src/util/random.rs @@ -1,6 +1,8 @@ +//! Secure random string generator + use rand::{distributions::Alphanumeric, thread_rng, Rng}; -/// Generate random string based on [thread_rng] and [Alphanumeric]. +/// Generates a random string based on [thread_rng] and [Alphanumeric]. #[crate::export] pub fn generate_secure_random_string(length: u16) -> String { thread_rng() diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index d8bbbf74ad..1ed11bbbc2 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -94,6 +94,7 @@ export default async (ctx: Koa.Context) => { // Compare passwords const same = verifyPassword(password, profile.password!); + // Update the password hashing algorithm if (same && isOldPasswordAlgorithm(profile.password!)) { profile.password = hashPassword(password); await UserProfiles.save(profile); diff --git a/packages/macro-rs/src/lib.rs b/packages/macro-rs/src/lib.rs index 3a1b972b9c..d723868f83 100644 --- a/packages/macro-rs/src/lib.rs +++ b/packages/macro-rs/src/lib.rs @@ -97,16 +97,16 @@ pub fn ts_export( /// ``` /// #[macro_rs::napi(object)] /// struct Person { -/// id: i32, -/// name: String, +/// id: i32, +/// name: String, /// } /// ``` /// simply becomes /// ``` /// #[napi_derive::napi(use_nullable = true, object)] /// struct Person { -/// id: i32, -/// name: String, +/// id: i32, +/// name: String, /// } /// ``` ///