commit fac5c193cb0faed029c049eaa4538f8bf9269144
parent ae4c3f76eef6a15a54860d6283eec80960e22651
Author: Silas Brack <silasbrack@gmail.com>
Date: Sun, 7 Jun 2026 14:25:43 +0200
feat: implement user registration via trailbase API
- Call POST /api/auth/v1/register instead of login for registration
- Add reqwest dependency for direct HTTP calls to trailbase
- Add password confirmation field to register form
- Validate passwords match before submitting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
6 files changed, 231 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -517,6 +517,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "eventsource-stream"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -562,6 +572,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -833,6 +858,22 @@ dependencies = [
]
[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1125,6 +1166,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[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.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1197,6 +1244,23 @@ dependencies = [
]
[[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 = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1265,12 +1329,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
+name = "openssl"
+version = "0.10.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
+name = "openssl-sys"
+version = "0.9.116"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1366,6 +1467,12 @@ dependencies = [
]
[[package]]
+name = "pkg-config"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+
+[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1548,6 +1655,46 @@ dependencies = [
[[package]]
name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
@@ -1647,6 +1794,19 @@ dependencies = [
]
[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "rustls"
version = "0.23.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1912,6 +2072,7 @@ dependencies = [
"axum",
"axum-extra",
"chrono",
+ "reqwest 0.12.28",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -2024,6 +2185,19 @@ dependencies = [
]
[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.4",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2146,6 +2320,16 @@ dependencies = [
]
[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2260,7 +2444,7 @@ dependencies = [
"http-body-util",
"jsonwebtoken",
"parking_lot",
- "reqwest",
+ "reqwest 0.13.2",
"serde",
"serde_json",
"serde_repr",
@@ -2318,6 +2502,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -13,5 +13,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tracing = "0.1"
+reqwest = { version = "0.12", features = ["json"] }
trailbase-client = "0.8"
url = "2"
diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs
@@ -82,6 +82,7 @@ pub struct RegisterForm {
pub username: String,
pub email: String,
pub password: String,
+ pub password_confirm: String,
}
pub async fn register(
@@ -113,6 +114,10 @@ pub async fn register(
));
}
+ if form.password != form.password_confirm {
+ return Err(render_register_error(&form, "Passwords do not match"));
+ }
+
let client = trailbase::new_client(&state.trailbase_url).map_err(|e| {
render_register_error(&form, &format!("Client error: {}", e))
})?;
diff --git a/src/trailbase.rs b/src/trailbase.rs
@@ -644,8 +644,33 @@ pub async fn login(client: &Client, email: &str, password: &str) -> Result<Store
}
pub async fn register(client: &Client, email: &str, password: &str) -> Result<StoredTokens, ClientError> {
- // Try to register - the client might expose this or we handle it differently
- // For now, attempt login which may auto-create if configured
+ #[derive(Serialize)]
+ struct RegisterRequest<'a> {
+ email: &'a str,
+ password: &'a str,
+ password_repeat: &'a str,
+ }
+
+ let url = format!("{}api/auth/v1/register", client.base_url());
+ let http_client = reqwest::Client::new();
+ let response = http_client
+ .post(&url)
+ .json(&RegisterRequest {
+ email,
+ password,
+ password_repeat: password,
+ })
+ .send()
+ .await
+ .map_err(|e| ClientError::Auth(format!("HTTP error: {}", e)))?;
+
+ if !response.status().is_success() {
+ let status = response.status();
+ let body = response.text().await.unwrap_or_default();
+ return Err(ClientError::Auth(format!("Registration failed ({}): {}", status, body)));
+ }
+
+ // After registration, log in to get tokens
client.login(email, password).await?;
let tokens = client.tokens().ok_or_else(|| {
diff --git a/templates/register.html b/templates/register.html
@@ -24,6 +24,11 @@
<input type="password" id="password" name="password" required minlength="8" autocomplete="new-password" />
</div>
+ <div class="form-group">
+ <label for="password_confirm">Confirm Password</label>
+ <input type="password" id="password_confirm" name="password_confirm" required minlength="8" autocomplete="new-password" />
+ </div>
+
<div class="form-actions">
<button type="submit">Register</button>
</div>
diff --git a/traildepot/config.textproto b/traildepot/config.textproto
@@ -36,7 +36,6 @@ record_apis: [{
}, {
name: "vote"
table_name: "vote"
- acl_world: []
acl_authenticated: [READ, CREATE, DELETE]
delete_access_rule: "_ROW_.user_id = _USER_.id"
}, {
@@ -45,4 +44,4 @@ record_apis: [{
acl_world: [READ]
acl_authenticated: [READ, CREATE, UPDATE]
update_access_rule: "_ROW_.user_id = _USER_.id"
-}]
+}]
+\ No newline at end of file