(feat): improvements to the slow

This commit is contained in:
Alois 2026-07-03 00:36:15 +02:00
commit c51c0f2e83
7 changed files with 76 additions and 21 deletions

1
Cargo.lock generated
View file

@ -1888,6 +1888,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"sha2 0.10.9",
"thiserror 2.0.18",
"tokio",
"url",

View file

@ -2,7 +2,7 @@
name = "tauth-sdk"
version = "0.1.0"
edition = "2024"
license = "UNLICENSED"
license = "MIT"
[lib]
name = "tauth_sdk"
@ -14,6 +14,7 @@ mtp = { git = "https://git.methanium.net/methanium/mtp.git", features = ["client
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
thiserror = "2"
tokio = { version = "1", features = ["rt", "time"] }
url = "2"

View file

@ -48,7 +48,7 @@ let client = TAuthClient::from_config(
"https://my.cool.app/tauth/callback",
std::env::var("TAUTH_PRIVATE_KEY")?,
)
.frontend_url("https://tauth.tensamin.net/login"),
.frontend_url("https://app.tensamin.net"),
)?;
```

View file

@ -30,6 +30,8 @@ Set `TAUTH_PRIVATE_KEY` in `backend/.env`. Generate keys with:
pnpm --filter @tauth-example/backend exec cargo run --bin generate_keys
```
Publish the generated `TAUTH_PUBLIC_KEY_SHA256` value in the `tauth.<identifier>` TXT record. TAuth receives the full public key during authorization and verifies it against that hash.
For local callback testing, `TAUTH_REDIRECT_URL` must be reachable by TAuth. Use a tunnel if needed.
## Run

View file

@ -1990,6 +1990,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"sha2 0.10.9",
"thiserror 2.0.18",
"tokio",
"url",

View file

@ -3,4 +3,5 @@ fn main() {
println!("TAUTH_PRIVATE_KEY={}", keys.private);
println!("TAUTH_PUBLIC_KEY={}", keys.public);
println!("TAUTH_PUBLIC_KEY_SHA256={}", keys.public_hash);
}

View file

@ -5,10 +5,11 @@ use mtp::crypto::{Keyring, PublicKeyBundle};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::time::Duration;
use url::Url;
const DEFAULT_FRONTEND_URL: &str = "https://tauth.tensamin.net/login";
const DEFAULT_FRONTEND_URL: &str = "https://app.tensamin.net";
const OMEGA_API: &str = "https://omega.tensamin.net/api";
#[derive(Debug, thiserror::Error)]
@ -39,6 +40,7 @@ pub enum TAuthError {
pub struct TAuthKeyPair {
pub private: String,
pub public: String,
pub public_hash: String,
}
pub type TAuthKeys = TAuthKeyPair;
@ -168,7 +170,11 @@ impl TAuthClient {
redirect_url: impl AsRef<str>,
private_key: impl AsRef<str>,
) -> Result<Self, TAuthError> {
Self::from_config(TAuthConfig::new(identifier, redirect_url.as_ref(), private_key.as_ref()))
Self::from_config(TAuthConfig::new(
identifier,
redirect_url.as_ref(),
private_key.as_ref(),
))
}
pub fn with_frontend_url(
@ -205,6 +211,10 @@ impl TAuthClient {
encode_bytes(&self.public_key_bundle.as_bytes())
}
pub fn public_key_hash(&self) -> String {
sha256_hex(self.public_key().as_bytes())
}
pub fn redirect_url(&self) -> &Url {
&self.redirect_url
}
@ -217,6 +227,7 @@ impl TAuthClient {
TAuthKeyPair {
private: encode_bytes(&self.keyring.to_bytes()),
public: self.public_key(),
public_hash: self.public_key_hash(),
}
}
@ -238,7 +249,10 @@ impl TAuthClient {
link.to_string()
}
pub fn parse_callback(&self, callback_url: impl AsRef<str>) -> Result<AuthCallback, TAuthError> {
pub fn parse_callback(
&self,
callback_url: impl AsRef<str>,
) -> Result<AuthCallback, TAuthError> {
parse_callback(callback_url)
}
@ -314,8 +328,13 @@ impl TAuthClient {
host_public_key: impl AsRef<str>,
) -> Result<MTPConnection, TAuthError> {
let omikron = fetch_omikron(user_id).await?;
self.connect_or_register_to_omikron(user_id, session_id, omikron.ip_address, host_public_key)
.await
self.connect_or_register_to_omikron(
user_id,
session_id,
omikron.ip_address,
host_public_key,
)
.await
}
pub async fn connect_or_register_to_omikron(
@ -329,13 +348,15 @@ impl TAuthClient {
let config = ClientConfig::new(omikron_url.into())
.with_description(format!("tauth:{}:{user_id}", self.identifier))
.with_auth_timeout(self.auth_timeout);
Ok(MTPClient::auth_connect_or_register(
config,
session_id,
&self.keyring,
&host_public_key,
Ok(
MTPClient::auth_connect_or_register(
config,
session_id,
&self.keyring,
&host_public_key,
)
.await?,
)
.await?)
}
pub async fn connect_session(
@ -359,9 +380,11 @@ impl TAuthClient {
pub fn generate_key_pair() -> TAuthKeyPair {
let keyring = Keyring::generate();
let public = encode_bytes(&keyring.public_key_bundle().as_bytes());
TAuthKeyPair {
private: encode_bytes(&keyring.to_bytes()),
public: encode_bytes(&keyring.public_key_bundle().as_bytes()),
public_hash: sha256_hex(public.as_bytes()),
public,
}
}
@ -440,6 +463,13 @@ fn decode_public_key_bundle(value: &str) -> Result<PublicKeyBundle, TAuthError>
.map_err(|error| TAuthError::Key(error.to_string()))
}
fn sha256_hex(bytes: &[u8]) -> String {
Sha256::digest(bytes)
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
async fn fetch_omikron(user_id: u64) -> Result<OmikronResponse, TAuthError> {
let url = format!("{OMEGA_API}/get/omikron/{user_id}");
Ok(reqwest::get(url).await?.error_for_status()?.json().await?)
@ -456,6 +486,7 @@ mod tests {
let public = decode_public_key_bundle(&keys.public).unwrap();
assert_eq!(public.as_bytes(), keyring.public_key_bundle().as_bytes());
assert_eq!(keys.public_hash, sha256_hex(keys.public.as_bytes()));
}
#[test]
@ -470,9 +501,28 @@ mod tests {
.unwrap();
let link = Url::parse(&client.generate_link(Some("abc123"))).unwrap();
assert_eq!(link.query_pairs().find(|(key, _)| key == "identifier").unwrap().1, "test.app");
assert_eq!(link.query_pairs().find(|(key, _)| key == "challenge").unwrap().1, "abc123");
assert!(link.query_pairs().any(|(key, value)| key == "public_key" && !value.is_empty()));
assert_eq!(
link.query_pairs()
.find(|(key, _)| key == "identifier")
.unwrap()
.1,
"test.app"
);
assert_eq!(
link.query_pairs()
.find(|(key, _)| key == "challenge")
.unwrap()
.1,
"abc123"
);
assert!(
link.query_pairs()
.any(|(key, value)| key == "public_key" && !value.is_empty())
);
assert_eq!(
client.public_key_hash(),
sha256_hex(client.public_key().as_bytes())
);
}
#[test]
@ -491,10 +541,9 @@ mod tests {
#[test]
fn parses_snake_case_host_key_callback_url() {
let callback = parse_callback(
"https://app.example/callback?userId=42&sessionId=7&host_public_key=c",
)
.unwrap();
let callback =
parse_callback("https://app.example/callback?userId=42&sessionId=7&host_public_key=c")
.unwrap();
assert_eq!(callback.host_public_key.as_deref(), Some("c"));
}