diff options
author | Tom Smeding <tom@tomsmeding.com> | 2025-01-20 20:27:17 +0100 |
---|---|---|
committer | Tom Smeding <tom@tomsmeding.com> | 2025-01-20 20:27:17 +0100 |
commit | c1488d6c5871044c8a4c68dd8868f143f5db13e2 (patch) | |
tree | c5bc0f09a18b477bbaebf1878daf1295767a3d2a /server | |
parent | 5faf0c8b38003cea8f37167d73c4949b0c2dcc4c (diff) |
server: Logout, refresh token
Diffstat (limited to 'server')
-rw-r--r-- | server/src/db.rs | 52 | ||||
-rw-r--r-- | server/src/main.rs | 48 |
2 files changed, 83 insertions, 17 deletions
diff --git a/server/src/db.rs b/server/src/db.rs index 2bc3f9b..bad748d 100644 --- a/server/src/db.rs +++ b/server/src/db.rs @@ -101,16 +101,64 @@ fn generate_login_token() -> String { base64_encode(&bytes) } +fn current_time() -> i64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64 +} + pub async fn create_login_token(db: DB, username: &str) -> Result<String, ()> { let mut conn = db.lock().await; - let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let now = current_time(); let token = generate_login_token(); match sqlx::query("insert into Logins (user, token, expire) values ($1, $2, $3)") .bind(username) .bind(&token) - .bind(now as i64 + LOGIN_TOKEN_EXPIRE_SECS) + .bind(now + LOGIN_TOKEN_EXPIRE_SECS) .execute(conn.deref_mut()).await { Ok(_) => Ok(token), Err(_) => Err(()), } } + +pub async fn drop_token(db: DB, token: &str) { + let mut conn = db.lock().await; + // ignore errors + let _ = sqlx::query("delete from Logins where token = $1") + .bind(token) + .execute(conn.deref_mut()).await; +} + +async fn set_token_expire(conn: &mut SqliteConnection, token: &str, expire: i64) -> Result<(), String> { + match sqlx::query("update Logins set expire = $1 where token = $2") + .bind(expire) + .bind(token) + .execute(conn).await { + Ok(_) => Ok(()), + Err(err) => { + eprintln!("set_token_expire: err = {err}"); + Err("Server error".to_string()) + } + } +} + +pub async fn maybe_refresh_token(db: DB, token: &str) -> Result<(), String> { + let mut conn = db.lock().await; + let now = current_time(); + match sqlx::query("select expire from Logins where token = $1") + .bind(token) + .fetch_optional(conn.deref_mut()).await { + Ok(Some(row)) => { + if now >= row.get::<i64, _>(0) - LOGIN_TOKEN_REFRESH_MARGIN { + set_token_expire(conn.deref_mut(), token, now + LOGIN_TOKEN_EXPIRE_SECS).await + } else { + Ok(()) + } + }, + Ok(None) => { + Err("Not logged in".to_string()) + }, + Err(err) => { + eprintln!("maybe_refresh_token: err = {err}"); + Err("Server error".to_string()) + } + } +} diff --git a/server/src/main.rs b/server/src/main.rs index 11fddb4..547086b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,18 +17,6 @@ mod constants; use constants::*; mod util; mod db; use db::DB; -async fn handle_ping() -> Result<String, Rejection> { - return Ok("pong".to_string()) -} - -#[derive(Deserialize)] -struct RegisterReq { - username: String, - password: String, -} - -type Response = Result<Box<dyn Reply>, Rejection>; - macro_rules! mk_bad_request { ($res:expr) => { Ok(Box::new(warp::reply::with_status($res, warp::http::StatusCode::BAD_REQUEST))) } } @@ -41,6 +29,24 @@ macro_rules! mk_server_err { () => { Ok(Box::new(warp::reply::with_status("Internal server error", warp::http::StatusCode::INTERNAL_SERVER_ERROR))) } } +type Response = Result<Box<dyn Reply>, Rejection>; + +async fn handle_ping(db: DB, mtoken: Option<String>) -> Response { + if let Some(token) = mtoken { + match db::maybe_refresh_token(db, &token).await { + Ok(()) => {}, + Err(err) => { return mk_bad_request!(err); }, + } + } + return Ok(Box::new("pong")) +} + +#[derive(Deserialize)] +struct RegisterReq { + username: String, + password: String, +} + async fn handle_register(db: DB, req: RegisterReq) -> Response { let salt = SaltString::generate(&mut OsRng); let hash = match Argon2::default().hash_password(req.password.as_bytes(), &salt) { @@ -53,7 +59,7 @@ async fn handle_register(db: DB, req: RegisterReq) -> Response { } } -async fn handle_login(db: DB, req: RegisterReq) -> Result<Box<dyn Reply>, Rejection> { +async fn handle_login(db: DB, req: RegisterReq) -> Response { let passhash = match db::get_passhash(db.clone(), &req.username).await { Ok(passhash) => passhash, Err(()) => return mk_not_found!("User not found"), @@ -77,6 +83,11 @@ async fn handle_login(db: DB, req: RegisterReq) -> Result<Box<dyn Reply>, Reject } } +async fn handle_logout(db: DB, token: String) -> Response { + db::drop_token(db, &token).await; + Ok(Box::new("Logged out")) +} + macro_rules! db_handler1 { ($db:expr, $handler:ident) => { { let db2 = $db.clone(); move |a| $handler(db2.clone(), a) } } } @@ -86,15 +97,22 @@ async fn main() { let db: DB = Arc::new(Mutex::new(db::open().await)); println!("Opened database at {DB_FILE_NAME}."); + let use_login_token = warp::header::<String>("x-kaasnoot-token"); + let use_optional_login_token = warp::header::optional::<String>("x-kaasnoot-token"); + let router = (warp::get().and(warp::path!("ping")) - .and_then(handle_ping)) + .and(use_optional_login_token) + .and_then(db_handler1!(db, handle_ping))) .or(warp::post().and(warp::path!("register")) .and(warp::body::json()) .and_then(db_handler1!(db, handle_register))) .or(warp::post().and(warp::path!("login")) .and(warp::body::json()) - .and_then(db_handler1!(db, handle_login))); + .and_then(db_handler1!(db, handle_login))) + .or(warp::post().and(warp::path!("logout")) + .and(use_login_token) + .and_then(db_handler1!(db, handle_logout))); warp::serve(router) .run(([0, 0, 0, 0], 8775)) |