Validate the session token

A previous commit added authentication token checks. Auth tokens are replaced with session tokens, which can (and should) expire. This commit validates sessions, which now allows access to gated operations.
This commit is contained in:
Savanni D'Gerinel 2023-10-03 17:04:35 -04:00
parent 73293fd932
commit 17ad927187
6 changed files with 104 additions and 24 deletions

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ node_modules
dist
result
*.tgz
file-service/auth.sqlite
file-service/*.sqlite
file-service/*.sqlite-shm
file-service/*.sqlite-wal

51
Cargo.lock generated
View File

@ -416,7 +416,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.37",
]
[[package]]
@ -471,6 +471,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
[[package]]
name = "cookie"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
dependencies = [
"time 0.3.29",
"version_check 0.9.4",
]
[[package]]
name = "cool_asserts"
version = "2.0.3"
@ -661,6 +671,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]]
name = "digest"
version = "0.10.7"
@ -842,6 +858,7 @@ dependencies = [
"bytes",
"chrono",
"clap 4.4.6",
"cookie",
"cool_asserts",
"futures-util",
"hex-string",
@ -1744,7 +1761,7 @@ dependencies = [
"log 0.3.9",
"mime 0.2.6",
"num_cpus",
"time",
"time 0.1.45",
"traitobject",
"typeable",
"unicase 1.4.2",
@ -2178,7 +2195,7 @@ checksum = "6c9172cb4c2f6c52117e25570983edcbb322f130b1031ae5d5d6b1abe7eeb493"
dependencies = [
"iron",
"log 0.3.9",
"time",
"time 0.1.45",
]
[[package]]
@ -3942,6 +3959,34 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
dependencies = [
"deranged",
"itoa",
"serde 1.0.188",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.4"

View File

@ -25,9 +25,12 @@ required-features = [ "auth-cli" ]
[target.auth-cli.dependencies]
[dependencies]
base64ct = { version = "1", features = [ "alloc" ] }
build_html = { version = "2" }
bytes = { version = "1" }
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4", features = [ "derive" ], optional = true }
cookie = { version = "0.17" }
futures-util = { version = "0.3" }
hex-string = "0.1.0"
http = { version = "0.2" }
@ -45,8 +48,6 @@ thiserror = "1.0.20"
tokio = { version = "1", features = [ "full" ] }
uuid = { version = "0.4", features = [ "serde", "v4" ] }
warp = { version = "0.3" }
base64ct = { version = "1", features = [ "alloc" ] }
clap = { version = "4", features = [ "derive" ], optional = true }
[dev-dependencies]
cool_asserts = { version = "2" }

View File

@ -10,9 +10,9 @@ pub async fn handle_index(
token: Option<SessionToken>,
) -> Result<Response<String>, Error> {
match token {
Some(token) => match app.auth_session(token).await {
Some(token) => match app.validate_session(token).await {
Ok(_) => render_gallery_page(app).await,
Err(err) => render_auth_page(Some(format!("authentication refused: {:?}", err))),
Err(err) => render_auth_page(Some(format!("session expired: {:?}", err))),
},
None => render_auth_page(None),
}
@ -76,10 +76,21 @@ pub async fn handle_auth(
) -> Result<http::Response<String>, Error> {
match form.get("token") {
Some(token) => match app
.auth_token(AuthToken::from(AuthToken::from(token.clone())))
.authenticate(AuthToken::from(AuthToken::from(token.clone())))
.await
{
Ok(_session_token) => render_gallery_page(app).await,
Ok(Some(session_token)) => Response::builder()
.header("location", "/")
.header(
"set-cookie",
format!(
"session={}; Secure; HttpOnly; SameSite=Strict",
session_token.to_string()
),
)
.status(StatusCode::SEE_OTHER)
.body("".to_owned()),
Ok(None) => render_auth_page(Some(format!("no user found"))),
Err(_) => render_auth_page(Some(format!("invalid auth token"))),
},
None => render_auth_page(Some(format!("no token available"))),

View File

@ -6,9 +6,10 @@ use http::status::StatusCode;
// use mustache::{compile_path, Template};
// use orizentic::{Permissions, ResourceName, Secret};
use bytes::Buf;
use cookie::Cookie;
use futures_util::StreamExt;
use std::{
collections::HashSet,
collections::{HashMap, HashSet},
convert::Infallible,
io::Read,
net::{IpAddr, Ipv4Addr, SocketAddr},
@ -126,12 +127,15 @@ impl App {
}
}
pub async fn auth_token(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
self.authdb.read().await.auth_token(token).await
pub async fn authenticate(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
self.authdb.read().await.authenticate(token).await
}
pub async fn auth_session(&self, token: SessionToken) -> Result<Option<Username>, AuthError> {
self.authdb.read().await.auth_session(token).await
pub async fn validate_session(
&self,
token: SessionToken,
) -> Result<Option<Username>, AuthError> {
self.authdb.read().await.validate_session(token).await
}
pub async fn list_files(&self) -> Result<HashSet<FileId>, ReadFileError> {
@ -159,9 +163,23 @@ fn maybe_with_session() -> impl Filter<Extract = (Option<SessionToken>,), Error
{
warp::any()
.and(warp::header::optional::<String>("cookie"))
.map(|cookies| {
println!("cookies retrieved: {:?}", cookies);
.map(|cookies| match cookies {
Some(cookies) => {
let c = Cookie::split_parse(cookies)
.collect::<Result<Vec<Cookie>, cookie::ParseError>>();
match c {
Ok(cookies) => {
for c in cookies {
if c.name() == "session" {
return Some(SessionToken::from(c.value()));
}
}
None
}
Err(_) => None,
}
}
None => None,
})
}

View File

@ -278,7 +278,7 @@ impl AuthDB {
Ok(usernames)
}
pub async fn auth_token(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
pub async fn authenticate(&self, token: AuthToken) -> Result<Option<SessionToken>, AuthError> {
let results = sqlx::query("SELECT * FROM users WHERE token = $1")
.bind(token.to_string())
.fetch_all(&self.pool)
@ -308,7 +308,10 @@ impl AuthDB {
Ok(Some(SessionToken::from(session_token)))
}
pub async fn auth_session(&self, token: SessionToken) -> Result<Option<Username>, AuthError> {
pub async fn validate_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",
)
@ -498,7 +501,7 @@ mod authdb_test {
let token = AuthToken::from("0000000000");
assert_matches!(db.auth_token(token).await, Ok(None));
assert_matches!(db.authenticate(token).await, Ok(None));
}
#[tokio::test]
@ -511,7 +514,7 @@ mod authdb_test {
.await
.expect("user to be created");
assert_matches!(db.auth_token(token).await, Ok(_));
assert_matches!(db.authenticate(token).await, Ok(_));
}
#[tokio::test]
@ -524,13 +527,13 @@ mod authdb_test {
.await
.expect("user to be created");
let session = db
.auth_token(token)
.authenticate(token)
.await
.expect("token authentication should succeed")
.expect("session token should be found");
assert_matches!(
db.auth_session(session).await,
db.validate_session(session).await,
Ok(Some(username)) => {
assert_eq!(username, Username::from("savanni"));
});