From 54aad9d841039844a69eb6b160309d3c954288a0 Mon Sep 17 00:00:00 2001
From: yuanzui-cf
Date: Wed, 18 Mar 2026 09:48:11 +0800
Subject: [PATCH 01/10] chore(build): Bump rust-version to 1.88
---
Cargo.toml | 1 +
Justfile | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index 04d41c4..5978947 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ description = "Universal Auth System for Grass Development Team."
repository = "https://github.com/Grass-Development-Team/auth"
license = "Apache-2.0"
edition = "2024"
+rust-version = "1.88"
[workspace.dependencies]
anyhow = "1"
diff --git a/Justfile b/Justfile
index cd9dd27..6dd58de 100644
--- a/Justfile
+++ b/Justfile
@@ -44,7 +44,7 @@ fmt-check:
cargo outdated -R
@msrv:
- cargo msrv find
+ cargo msrv find --min 1.85
# Combined quality check
@quality: audit outdated msrv fmt-check clippy test
From d01fccbb65d949aca314c7f69099df3f25a3ece9 Mon Sep 17 00:00:00 2001
From: yuanzui-cf
Date: Wed, 18 Mar 2026 12:45:55 +0800
Subject: [PATCH 02/10] chore: Switch TLS to rustls and update SeaORM features
---
Cargo.lock | 518 +++++++++++++++++++++++++++++------------------------
Cargo.toml | 20 ++-
2 files changed, 299 insertions(+), 239 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index fc9baca..3265842 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -55,56 +55,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "anstream"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
-
-[[package]]
-name = "anstyle-parse"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
-dependencies = [
- "anstyle",
- "once_cell_polyfill",
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "anyhow"
version = "1.0.102"
@@ -225,7 +175,7 @@ dependencies = [
"sea-orm-migration",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"token",
"tokio",
@@ -472,6 +422,12 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -508,52 +464,6 @@ dependencies = [
"stacker",
]
-[[package]]
-name = "clap"
-version = "4.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
-dependencies = [
- "clap_builder",
- "clap_derive",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
-dependencies = [
- "heck 0.5.0",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "clap_lex"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-
-[[package]]
-name = "colorchoice"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
-
[[package]]
name = "colored"
version = "3.1.1"
@@ -603,6 +513,16 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation"
version = "0.10.1"
@@ -668,7 +588,7 @@ dependencies = [
"rand",
"sha2",
"subtle",
- "thiserror",
+ "thiserror 2.0.18",
]
[[package]]
@@ -885,21 +805,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
-[[package]]
-name = "foreign-types"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-dependencies = [
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -1414,12 +1319,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "is_terminal_polyfill"
-version = "1.70.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
-
[[package]]
name = "itertools"
version = "0.13.0"
@@ -1435,6 +1334,28 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
[[package]]
name = "js-sys"
version = "0.3.91"
@@ -1500,13 +1421,15 @@ dependencies = [
"httpdate",
"idna",
"mime",
- "native-tls",
"nom",
"percent-encoding",
"quoted_printable",
+ "rustls",
+ "rustls-platform-verifier",
"socket2 0.6.3",
"tokio",
"url",
+ "webpki-roots 1.0.5",
]
[[package]]
@@ -1539,17 +1462,10 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
- "cc",
"pkg-config",
"vcpkg",
]
-[[package]]
-name = "linux-raw-sys"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-
[[package]]
name = "litemap"
version = "0.8.1"
@@ -1645,23 +1561,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "native-tls"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
[[package]]
name = "nom"
version = "8.0.0"
@@ -1758,36 +1657,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
-name = "once_cell_polyfill"
-version = "1.70.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
-
-[[package]]
-name = "openssl"
-version = "0.10.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
-dependencies = [
- "bitflags",
- "cfg-if",
- "foreign-types",
- "libc",
- "once_cell",
- "openssl-macros",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-macros"
-version = "0.1.1"
+name = "openssl-probe"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-probe"
@@ -1795,18 +1668,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
-[[package]]
-name = "openssl-sys"
-version = "0.9.112"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
-
[[package]]
name = "ordered-float"
version = "4.6.0"
@@ -2146,15 +2007,18 @@ dependencies = [
"futures-util",
"itertools",
"itoa",
- "native-tls",
"num-bigint",
"percent-encoding",
"pin-project-lite",
+ "rustls",
+ "rustls-native-certs 0.7.3",
+ "rustls-pemfile",
+ "rustls-pki-types",
"ryu",
"sha1_smol",
"socket2 0.5.10",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls",
"tokio-util",
"url",
]
@@ -2339,16 +2203,99 @@ dependencies = [
]
[[package]]
-name = "rustix"
-version = "1.1.4"
+name = "rustls"
+version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
- "bitflags",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys 0.61.2",
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
+dependencies = [
+ "openssl-probe 0.1.6",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework 2.11.1",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+dependencies = [
+ "openssl-probe 0.2.1",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework 3.7.0",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4910321ebe4151be888e35fe062169554e74aad01beafed60410131420ceffbc"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs 0.8.3",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework 3.7.0",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
]
[[package]]
@@ -2423,7 +2370,7 @@ dependencies = [
"serde_json",
"sqlx",
"strum",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"tracing",
"url",
@@ -2437,10 +2384,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94492e2ab6c045b4cc38013809ce255d14c3d352c9f0d11e6b920e2adc948ad"
dependencies = [
"chrono",
- "clap",
- "dotenvy",
"glob",
"regex",
+ "sea-schema",
+ "sqlx",
+ "tokio",
"tracing",
"tracing-subscriber",
"url",
@@ -2467,8 +2415,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7315c0cadb7e60fb17ee2bb282aa27d01911fc2a7e5836ec1d4ac37d19250bb4"
dependencies = [
"async-trait",
- "clap",
- "dotenvy",
"sea-orm",
"sea-orm-cli",
"sea-schema",
@@ -2520,7 +2466,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "thiserror",
+ "thiserror 2.0.18",
]
[[package]]
@@ -2531,7 +2477,9 @@ checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338"
dependencies = [
"futures",
"sea-query",
+ "sea-query-binder",
"sea-schema-derive",
+ "sqlx",
]
[[package]]
@@ -2552,6 +2500,19 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.4",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
[[package]]
name = "security-framework"
version = "3.7.0"
@@ -2559,7 +2520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@@ -2733,7 +2694,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
dependencies = [
"num-bigint",
"num-traits",
- "thiserror",
+ "thiserror 2.0.18",
"time",
]
@@ -2827,21 +2788,22 @@ dependencies = [
"indexmap",
"log",
"memchr",
- "native-tls",
"once_cell",
"percent-encoding",
"rust_decimal",
+ "rustls",
"serde",
"serde_json",
"sha2",
"smallvec",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"tokio",
"tokio-stream",
"tracing",
"url",
"uuid",
+ "webpki-roots 0.26.11",
]
[[package]]
@@ -2922,7 +2884,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"tracing",
"uuid",
@@ -2965,7 +2927,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"tracing",
"uuid",
@@ -2992,7 +2954,7 @@ dependencies = [
"serde",
"serde_urlencoded",
"sqlx-core",
- "thiserror",
+ "thiserror 2.0.18",
"time",
"tracing",
"url",
@@ -3035,12 +2997,6 @@ dependencies = [
"unicode-properties",
]
-[[package]]
-name = "strsim"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-
[[package]]
name = "strum"
version = "0.26.3"
@@ -3099,16 +3055,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
-name = "tempfile"
-version = "3.27.0"
+name = "thiserror"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "fastrand",
- "getrandom 0.4.2",
- "once_cell",
- "rustix",
- "windows-sys 0.61.2",
+ "thiserror-impl 1.0.69",
]
[[package]]
@@ -3117,7 +3069,18 @@ version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -3205,7 +3168,7 @@ dependencies = [
"redis",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 2.0.18",
"uuid",
]
@@ -3238,12 +3201,12 @@ dependencies = [
]
[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
+name = "tokio-rustls"
+version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
- "native-tls",
+ "rustls",
"tokio",
]
@@ -3520,12 +3483,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
-[[package]]
-name = "utf8parse"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-
[[package]]
name = "uuid"
version = "1.22.0"
@@ -3679,6 +3636,33 @@ dependencies = [
"semver",
]
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.5",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "whoami"
version = "1.6.1"
@@ -3757,6 +3741,15 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -3793,6 +3786,21 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -3824,6 +3832,12 @@ dependencies = [
"windows_x86_64_msvc 0.52.6",
]
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -3836,6 +3850,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -3848,6 +3868,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -3866,6 +3892,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -3878,6 +3910,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -3890,6 +3928,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -3902,6 +3946,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
diff --git a/Cargo.toml b/Cargo.toml
index 5978947..5662f33 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,14 +38,17 @@ regex = "1"
minijinja = "2"
sea-orm = { version = "1.1", features = [
- "sqlx-all",
- "runtime-tokio-native-tls",
+ "sqlx-postgres",
+ "runtime-tokio-rustls",
"macros",
"with-chrono",
"with-uuid",
] }
-sea-orm-migration = "1.1"
-redis = { version = "0.27.5", features = ["tokio-native-tls-comp"] }
+sea-orm-migration = { version = "1.1", default-features = false, features = [
+ "runtime-tokio-rustls",
+ "sqlx-postgres",
+] }
+redis = { version = "0.27.5", features = ["tokio-rustls-comp"] }
sha2 = "0.10.9"
argon2 = "0.5"
@@ -57,7 +60,14 @@ subtle = "2.6"
jsonwebtoken = "9.3.1"
uuid = { version = "1.16.0", features = ["v4"] }
-lettre = "0.11"
+lettre = { version = "0.11", default-features = false, features = [
+ "builder",
+ "hostname",
+ "pool",
+ "smtp-transport",
+ "rustls-tls",
+ "rustls-platform-verifier",
+] }
auth = { path = "auth" }
assets = { path = "crates/assets" }
From 91106f0f419d6fe899d577050644c149a764cce6 Mon Sep 17 00:00:00 2001
From: yuanzui-cf
Date: Wed, 18 Mar 2026 14:00:00 +0800
Subject: [PATCH 03/10] feat(auth): Enable Redis-backed registration tokens
- Introduce Redis-backed registration token issuance with Lua scripts
and new RegisterTokenLease
- Wire Redis token issuance into registration flow; pass redis handle to
email verification
- Define TTL constants for token and reuse window; expose Lease type in
token module
- Update email template to include verification link and expiry
information
---
auth/src/routers/controllers/auth.rs | 16 ++-
auth/src/services/auth/register.rs | 89 ++++++++++---
.../assets/templates/mails/register.html | 18 ++-
crates/token/src/services/mod.rs | 2 +-
crates/token/src/services/register_token.rs | 125 +++++++++++++++++-
5 files changed, 225 insertions(+), 25 deletions(-)
diff --git a/auth/src/routers/controllers/auth.rs b/auth/src/routers/controllers/auth.rs
index 24a963e..c7c81e1 100644
--- a/auth/src/routers/controllers/auth.rs
+++ b/auth/src/routers/controllers/auth.rs
@@ -3,6 +3,7 @@ use axum::{
http::StatusCode,
};
use axum_extra::extract::CookieJar;
+use redis::aio::MultiplexedConnection;
use token::services::SessionService;
use crate::{
@@ -23,8 +24,21 @@ pub async fn register(
State(state): State,
Json(req): Json,
) -> Response {
+ let mut redis: Option = if state.mail.is_some()
+ && let Ok(redis) = state.redis.get_multiplexed_tokio_connection().await
+ {
+ Some(redis)
+ } else {
+ None
+ };
+
match req
- .register(&state.db, &state.config, state.mail.as_deref())
+ .register(
+ &state.db,
+ &state.config,
+ state.mail.as_deref(),
+ redis.as_mut(),
+ )
.await
{
Ok(message) => Response::new(
diff --git a/auth/src/services/auth/register.rs b/auth/src/services/auth/register.rs
index 5daa669..7e5202d 100644
--- a/auth/src/services/auth/register.rs
+++ b/auth/src/services/auth/register.rs
@@ -2,9 +2,11 @@ use std::sync::OnceLock;
use crypto::password::{PasswordHashAlgorithm, PasswordManager};
use minijinja::context;
+use redis::aio::MultiplexedConnection;
use regex::Regex;
use sea_orm::{DatabaseConnection, TransactionError, TransactionTrait};
use serde::Deserialize;
+use token::services::RegisterTokenService;
use validator::Validatable;
use crate::{
@@ -17,6 +19,8 @@ use crate::{
};
static EMAIL_RE: OnceLock = OnceLock::new();
+const REGISTER_TOKEN_TTL_SECONDS: u64 = 60 * 60;
+const REGISTER_TOKEN_REUSE_MIN_TTL_SECONDS: u64 = 60;
#[derive(Deserialize)]
pub struct RegisterService {
@@ -32,6 +36,7 @@ impl RegisterService {
conn: &DatabaseConnection,
config: &Config,
mailer: Option<&Mailer>,
+ redis: Option<&mut MultiplexedConnection>,
) -> Result {
if !config.site.enable_registration {
return Err(AppError::biz(
@@ -42,15 +47,21 @@ impl RegisterService {
self.validate()?;
- if let Ok(user) = users::get_user_by_email(conn, &self.email).await {
+ if let Ok((user, _, _)) = users::get_user_by_email(conn, &self.email).await {
if let Some(mailer) = mailer
- && user.0.status.is_inactive()
+ && user.status.is_inactive()
{
- // TODO: Check if the verification token has expired
- return match self.send_verification_email(mailer, config).await {
- Ok(_) => Ok("Verification email sent successfully".into()),
- Err(err) => Err(err),
- };
+ Self::send_verification_email(
+ mailer,
+ redis,
+ config,
+ user.uid,
+ &user.username,
+ &user.email,
+ )
+ .await?;
+
+ return Ok("Verification email sent successfully".into());
}
return Err(AppError::biz(
@@ -122,31 +133,77 @@ impl RegisterService {
}
if let Some(mailer) = mailer {
- return match self.send_verification_email(mailer, config).await {
- Ok(_) => Ok("Verification email sent successfully".into()),
- Err(err) => Err(err),
- };
+ let (user, _, _) =
+ users::get_user_by_email(conn, &self.email)
+ .await
+ .map_err(|err| {
+ AppError::infra(
+ AppErrorKind::InternalError,
+ "auth.register.find_created_user",
+ err,
+ )
+ })?;
+
+ Self::send_verification_email(
+ mailer,
+ redis,
+ config,
+ user.uid,
+ &user.username,
+ &user.email,
+ )
+ .await?;
+ return Ok("Verification email sent successfully".into());
}
Ok("Register successfully".into())
}
async fn send_verification_email(
- &self,
mailer: &Mailer,
+ redis: Option<&mut MultiplexedConnection>,
config: &Config,
+ uid: i32,
+ username: &str,
+ email: &str,
) -> Result<(), AppError> {
- // TODO: Send verification link
+ let Some(redis) = redis else {
+ return Err(AppError::infra(
+ AppErrorKind::InternalError,
+ "auth.register.send_verification_email",
+ anyhow::anyhow!("Redis connection not available"),
+ ));
+ };
+
+ let token = RegisterTokenService::issue_or_reuse_for_user(
+ redis,
+ uid,
+ email,
+ REGISTER_TOKEN_TTL_SECONDS,
+ REGISTER_TOKEN_REUSE_MIN_TTL_SECONDS,
+ )
+ .await
+ .map_err(|err| {
+ AppError::infra(
+ AppErrorKind::InternalError,
+ "auth.register.issue_or_reuse_token",
+ err,
+ )
+ })?;
+ let expires_minutes = token.ttl_secs.saturating_add(59) / 60;
+
match mailer
.send_mail(
- &self.email,
+ email,
"Account registration received",
"register",
context! {
- username => self.username.clone(),
- email => self.email.clone(),
+ username => username.to_owned(),
+ email => email.to_owned(),
domain => config.domain.clone(),
site_name => config.site.name.clone(),
+ verification_token => token.token,
+ expires_minutes => expires_minutes,
},
)
.await
diff --git a/crates/assets/assets/templates/mails/register.html b/crates/assets/assets/templates/mails/register.html
index 9237382..11d0aee 100644
--- a/crates/assets/assets/templates/mails/register.html
+++ b/crates/assets/assets/templates/mails/register.html
@@ -70,12 +70,18 @@
website.
- Service URL:
- {{ domain }}
+ Verification link:
+
+ {{ domain
+ }}/actions/verify_email?token={{
+ verification_token }}
+
+
+
+ This token expires in
+ {{ expires_minutes }} minutes.
If you did not create this account, you can
diff --git a/crates/token/src/services/mod.rs b/crates/token/src/services/mod.rs
index e50f467..bf6ae85 100644
--- a/crates/token/src/services/mod.rs
+++ b/crates/token/src/services/mod.rs
@@ -3,5 +3,5 @@ pub mod register_token;
pub mod session;
pub use password_reset::{PasswordResetToken, PasswordResetTokenService};
-pub use register_token::{RegisterToken, RegisterTokenService};
+pub use register_token::{RegisterToken, RegisterTokenLease, RegisterTokenService};
pub use session::{Session, SessionLookup, SessionService};
diff --git a/crates/token/src/services/register_token.rs b/crates/token/src/services/register_token.rs
index 80a2c22..5992d38 100644
--- a/crates/token/src/services/register_token.rs
+++ b/crates/token/src/services/register_token.rs
@@ -1,8 +1,78 @@
+use std::sync::OnceLock;
+
use redis::aio::MultiplexedConnection;
use serde::{Deserialize, Serialize};
use crate::{TokenError, TokenStore};
+const ISSUE_OR_REUSE_SCRIPT: &str = r#"
+local index_key = KEYS[1]
+local token_prefix = ARGV[1]
+local uid = tonumber(ARGV[2])
+local email = ARGV[3]
+local ttl_secs = tonumber(ARGV[4])
+local min_reuse_ttl_secs = tonumber(ARGV[5])
+local new_token = ARGV[6]
+
+local existing = redis.call('GET', index_key)
+if existing then
+ local existing_key = token_prefix .. '::' .. existing
+ local payload = redis.call('GET', existing_key)
+ if payload then
+ local ok, decoded = pcall(cjson.decode, payload)
+ if ok and decoded and tonumber(decoded.uid) == uid and decoded.email == email then
+ local ttl = redis.call('TTL', existing_key)
+ if ttl > min_reuse_ttl_secs then
+ local index_ttl = redis.call('TTL', index_key)
+ if index_ttl < ttl then
+ redis.call('EXPIRE', index_key, ttl)
+ end
+ return {existing, ttl}
+ end
+ end
+ end
+ redis.call('DEL', existing_key)
+end
+
+local payload = cjson.encode({ uid = uid, email = email })
+local new_key = token_prefix .. '::' .. new_token
+redis.call('SETEX', new_key, ttl_secs, payload)
+redis.call('SETEX', index_key, ttl_secs, new_token)
+return {new_token, ttl_secs}
+"#;
+
+const CONSUME_AND_CLEAR_INDEX_SCRIPT: &str = r#"
+local token_key = KEYS[1]
+local index_prefix = ARGV[1]
+local token = ARGV[2]
+
+local payload = redis.call('GETDEL', token_key)
+if not payload then
+ return nil
+end
+
+local ok, decoded = pcall(cjson.decode, payload)
+if ok and decoded and decoded.uid ~= nil then
+ local index_key = index_prefix .. '::' .. tostring(decoded.uid)
+ local indexed_token = redis.call('GET', index_key)
+ if indexed_token == token then
+ redis.call('DEL', index_key)
+ end
+end
+
+return payload
+"#;
+
+fn issue_or_reuse_script() -> &'static redis::Script {
+ static SCRIPT: OnceLock = OnceLock::new();
+ SCRIPT.get_or_init(|| redis::Script::new(ISSUE_OR_REUSE_SCRIPT))
+}
+
+fn consume_and_clear_index_script() -> &'static redis::Script {
+ static SCRIPT: OnceLock = OnceLock::new();
+ SCRIPT.get_or_init(|| redis::Script::new(CONSUME_AND_CLEAR_INDEX_SCRIPT))
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterToken {
pub uid: i32,
@@ -12,6 +82,12 @@ pub struct RegisterToken {
#[derive(Debug, Clone, Copy, Default)]
pub struct RegisterTokenService;
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct RegisterTokenLease {
+ pub token: String,
+ pub ttl_secs: u64,
+}
+
#[async_trait::async_trait]
impl TokenStore for RegisterTokenService {
type Payload = RegisterToken;
@@ -20,6 +96,16 @@ impl TokenStore for RegisterTokenService {
}
impl RegisterTokenService {
+ const INDEX_PREFIX: &'static str = "register-token-index";
+
+ fn index_key(uid: i32) -> String {
+ format!("{}::{uid}", Self::INDEX_PREFIX)
+ }
+
+ fn token_key(token: &str) -> String {
+ format!("{}::{token}", Self::PREFIX)
+ }
+
pub async fn issue_for_user(
redis: &mut MultiplexedConnection,
uid: i32,
@@ -33,10 +119,47 @@ impl RegisterTokenService {
::issue(redis, &token, ttl_secs).await
}
+ pub async fn issue_or_reuse_for_user(
+ redis: &mut MultiplexedConnection,
+ uid: i32,
+ email: impl Into,
+ ttl_secs: u64,
+ min_reuse_ttl_secs: u64,
+ ) -> Result {
+ let email = email.into();
+ let new_token = uuid::Uuid::new_v4().to_string();
+ let index_key = Self::index_key(uid);
+ let mut invocation = issue_or_reuse_script().prepare_invoke();
+ invocation
+ .key(index_key)
+ .arg(Self::PREFIX)
+ .arg(uid)
+ .arg(email)
+ .arg(ttl_secs)
+ .arg(min_reuse_ttl_secs)
+ .arg(new_token);
+ let (token, ttl_secs): (String, i64) = invocation.invoke_async(redis).await?;
+
+ Ok(RegisterTokenLease {
+ token,
+ ttl_secs: ttl_secs.max(0) as u64,
+ })
+ }
+
pub async fn consume(
redis: &mut MultiplexedConnection,
token: &str,
) -> Result