You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yuurei/src/state/user.rs

107 lines
2.5 KiB

//! Used to determine which post belongs to who.
//!
//! Mostly for determining if a poster owns a post.
use super::*;
use std::{
net::SocketAddr,
};
use cryptohelpers::sha256;
/// A user's unique ID.
///
/// This is composed by the user's address and their session ID.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct UserID(SocketAddr, session::SessionID);
static COUNTER: GlobalCounter = GlobalCounter::new();
impl UserID
{
/// Generate a token from this instance.
///
/// User tokens are deterministically generated and can be deterministically verified.
pub fn generate_token(&self) -> u64
{
let cnt = COUNTER.get();
let mut trunc = [0u8; std::mem::size_of::<u64>()];
let hash = GloballySalted::new(self).compute_sha256_hash();
bytes::move_slice(&mut trunc[..], hash.as_ref());
u64::from_le_bytes(trunc) ^ cnt
}
/// Validate a token for this instance created with `generate_token`.
pub fn validate_token(&self, val: u64) -> bool
{
let mut trunc = [0u8; std::mem::size_of::<u64>()];
let hash = GloballySalted::new(self).compute_sha256_hash();
bytes::move_slice(&mut trunc[..], hash.as_ref());
COUNTER.valid(u64::from_le_bytes(trunc) ^ val)
}
}
/// A user not bound to a session.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct User
{
addr: SocketAddr,
}
impl User
{
/// Get the user ID for this session.
pub fn id_for_session(&self, session: &session::Session) -> UserID
{
UserID(self.addr, session.session_id().clone())
}
}
#[cfg(test)]
mod tests
{
use super::*;
use tokio::sync::mpsc;
use tokio::time;
use std::net::SocketAddrV4;
use std::net::Ipv4Addr;
#[tokio::test]
async fn counter_tokens()
{
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80));
let usr = User{addr};
let ses = session::Session::create(usr);
let id = ses.user_id();
let (mut tx, mut rx) = mpsc::channel(5);
let task = tokio::spawn(async move {
let id = ses.user_id();
while let Some(token) = rx.recv().await {
if !id.validate_token(token) {
panic!("Failed to validate token {:x} for id {:?}", token, id);
} else {
eprintln!("Token {:x} valid for id {:?}", token, id);
}
}
});
for x in 1..=10
{
if x % 2 == 0 {
time::delay_for(time::Duration::from_millis(10 * x)).await;
}
if tx.send(id.generate_token()).await.is_err() {
eprintln!("Failed to send to task");
break;
}
}
drop(tx);
task.await.expect("Background validate task failed");
}
}