Add session checks
This commit is contained in:
parent
5c80fb3591
commit
2ad3874724
|
@ -5,8 +5,8 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
token TEXT NOT NULL
|
token TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS session_tokens (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
token TEXT NOT NULL,
|
token TEXT NOT NULL,
|
||||||
user INTEGER,
|
user_id INTEGER,
|
||||||
FOREIGN KEY(user) REFERENCES user(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -67,6 +67,12 @@ pub enum ReadFileError {
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
|
#[error("authentication token is duplicated")]
|
||||||
|
DuplicateAuthToken,
|
||||||
|
|
||||||
|
#[error("session token is duplicated")]
|
||||||
|
DuplicateSessionToken,
|
||||||
|
|
||||||
#[error("database failed")]
|
#[error("database failed")]
|
||||||
SqlError(sqlx::Error),
|
SqlError(sqlx::Error),
|
||||||
}
|
}
|
||||||
|
@ -250,11 +256,11 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth_token(&self, token: AuthToken) -> Result<SessionToken, AuthError> {
|
pub async fn auth_token(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
|
||||||
self.authdb.read().await.auth_token(token).await
|
self.authdb.read().await.auth_token(token).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth_session(&self, token: SessionToken) -> Result<Username, AuthError> {
|
pub async fn auth_session(&self, token: SessionToken) -> Result<Option<Username>, AuthError> {
|
||||||
self.authdb.read().await.auth_session(token).await
|
self.authdb.read().await.auth_session(token).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,10 +295,9 @@ impl AuthDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_user(&self, username: Username) -> Result<AuthToken, AuthError> {
|
async fn add_user(&self, username: Username) -> Result<AuthToken, AuthError> {
|
||||||
let auth_plaintext = format!("{}:{}", Uuid::new_v4(), username.to_string());
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(auth_plaintext);
|
hasher.update(Uuid::new_v4().hyphenated().to_string());
|
||||||
|
hasher.update(username.to_string());
|
||||||
let auth_token = Base64::encode_string(&hasher.finalize());
|
let auth_token = Base64::encode_string(&hasher.finalize());
|
||||||
|
|
||||||
let _ = sqlx::query("INSERT INTO users (username, token) VALUES ($1, $2)")
|
let _ = sqlx::query("INSERT INTO users (username, token) VALUES ($1, $2)")
|
||||||
|
@ -308,22 +313,57 @@ impl AuthDB {
|
||||||
let usernames = sqlx::query_as::<_, Username>("SELECT (username) FROM users")
|
let usernames = sqlx::query_as::<_, Username>("SELECT (username) FROM users")
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
/*
|
|
||||||
let usernames = result
|
|
||||||
.into_iter()
|
|
||||||
.map(|row| Username::from(row.column(0)))
|
|
||||||
.collect::<Vec<Username>>();
|
|
||||||
*/
|
|
||||||
|
|
||||||
Ok(usernames)
|
Ok(usernames)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_token(&self, _token: AuthToken) -> Result<SessionToken, AuthError> {
|
async fn auth_token(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
|
||||||
unimplemented!()
|
let results = sqlx::query("SELECT * FROM users WHERE token = $1")
|
||||||
|
.bind(token.to_string())
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if results.len() > 1 {
|
||||||
|
return Err(AuthError::DuplicateAuthToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_session(&self, _token: SessionToken) -> Result<Username, AuthError> {
|
if results.len() == 0 {
|
||||||
unimplemented!()
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_id: i64 = results[0].try_get("id")?;
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(Uuid::new_v4().hyphenated().to_string());
|
||||||
|
hasher.update(token.to_string());
|
||||||
|
let session_token = Base64::encode_string(&hasher.finalize());
|
||||||
|
|
||||||
|
let _ = sqlx::query("INSERT INTO sessions (token, user_id) VALUES ($1, $2)")
|
||||||
|
.bind(session_token.clone())
|
||||||
|
.bind(user_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(SessionToken::from(session_token)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn auth_session(&self, token: SessionToken) -> Result<Option<Username>, AuthError> {
|
||||||
|
let rows = sqlx::query(
|
||||||
|
"SELECT users.username FROM sessions INNER JOIN users ON sessions.user_id = users.id WHERE sessions.token = $1",
|
||||||
|
)
|
||||||
|
.bind(token.to_string())
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await?;
|
||||||
|
if rows.len() > 1 {
|
||||||
|
return Err(AuthError::DuplicateSessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.len() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let username: String = rows[0].try_get("username")?;
|
||||||
|
Ok(Some(Username::from(username)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,13 +525,53 @@ mod authdb_test {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn can_authenticate_token() {
|
async fn unknown_auth_token_returns_nothing() {
|
||||||
unimplemented!()
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let _ = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
|
||||||
|
let token = AuthToken::from("0000000000");
|
||||||
|
|
||||||
|
assert_matches!(db.auth_token(token).await, Ok(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn can_validate_session_token() {
|
async fn auth_token_becomes_session_token() {
|
||||||
unimplemented!()
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let token = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
|
||||||
|
assert_matches!(db.auth_token(token).await, Ok(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_validate_session_token() {
|
||||||
|
let db = AuthDB::new(PathBuf::from(":memory:"))
|
||||||
|
.await
|
||||||
|
.expect("a memory-only database will be created");
|
||||||
|
let token = db
|
||||||
|
.add_user(Username::from("savanni"))
|
||||||
|
.await
|
||||||
|
.expect("user to be created");
|
||||||
|
let session = db
|
||||||
|
.auth_token(token)
|
||||||
|
.await
|
||||||
|
.expect("token authentication should succeed")
|
||||||
|
.expect("session token should be found");
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
db.auth_session(session).await,
|
||||||
|
Ok(Some(username)) => {
|
||||||
|
assert_eq!(username, Username::from("savanni"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue