diff --git a/src/api/mod.rs b/src/api/mod.rs index 66a36ae..0e3126a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -5,6 +5,7 @@ use axum::{ }; use crate::{ + database::DatabaseError, tags::TagError, users::{UserError, auth::AuthError, sessions::SessionError}, }; @@ -23,6 +24,7 @@ pub fn api_router() -> Router { .route("/api/users/{id}", get(users::get_by_id)) .route("/api/users/@{handle}", get(users::get_by_handle)) .route("/api/sessions/{id}", get(sessions::get_by_id)) + .route("/api/sessions/{id}/revoke", post(sessions::revoke_by_id)) .route("/api/tags/{id}", get(tags::get_by_id)) .route("/api/tags/#{name}", get(tags::get_by_name)) } @@ -45,4 +47,4 @@ macro_rules! composite_from { )+ }; } -composite_from!(AuthError, UserError, SessionError, TagError); +composite_from!(AuthError, UserError, SessionError, TagError, DatabaseError); diff --git a/src/api/sessions.rs b/src/api/sessions.rs index ca5b525..cc1b8f8 100644 --- a/src/api/sessions.rs +++ b/src/api/sessions.rs @@ -1,7 +1,7 @@ use axum::{ Json, extract::Path, - http::HeaderMap, + http::{HeaderMap, StatusCode}, response::{IntoResponse, Response}, }; use uuid::Uuid; @@ -31,3 +31,29 @@ pub async fn get_by_id( false => Err(SessionError::NoSessionWithId(id))?, } } + +pub async fn revoke_by_id( + Path(id): Path, + headers: HeaderMap, +) -> Result { + let u = User::authenticate(&headers)?.required()?; + let mut s = Session::get_by_id(id)?; + + match s.user_id == u.id + || u.has_permission(Permission::RevokeOthersSessions) + .is_ok_and(|v| v) + { + true => { + s.revoke(Some(&u))?; + Ok(Json(s).into_response()) + } + false => match u.has_permission(Permission::ListOthersSessions)? { + true => Ok(( + StatusCode::FORBIDDEN, + "You don't have permission to revoke this session.", + ) + .into_response()), + false => Err(SessionError::NoSessionWithId(id))?, + }, + } +} diff --git a/src/database/mod.rs b/src/database/mod.rs index 32a492b..99e9018 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,5 +1,6 @@ use std::{env, error::Error, sync::LazyLock}; +use axum::{http::StatusCode, response::IntoResponse}; use rusqlite::{Connection, OptionalExtension}; macro_rules! migration { @@ -21,6 +22,16 @@ CREATE TABLE IF NOT EXISTS migrations ( ); "#; +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +pub struct DatabaseError(#[from] rusqlite::Error); +impl IntoResponse for DatabaseError { + fn into_response(self) -> axum::response::Response { + println!("[DB ERROR] {}", self.to_string()); + (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error.").into_response() + } +} + pub fn conn() -> Result { let conn = Connection::open(&*DB_URL)?; for pragma in CONNECTION_PRAGMAS { diff --git a/src/users/permissions.rs b/src/users/permissions.rs index f6fa0e6..5b1a6f5 100644 --- a/src/users/permissions.rs +++ b/src/users/permissions.rs @@ -1,13 +1,15 @@ -use crate::users::User; +use crate::{database::DatabaseError, users::User}; /// Infradmin and systemuser have all permissions. pub enum Permission { // All Users have the right to observe their own sessions ListOthersSessions, + // All Users have the right to revoke their own sessions + RevokeOthersSessions, } impl User { - pub fn has_permission(&self, permission: Permission) -> Result { + pub fn has_permission(&self, permission: Permission) -> Result { if self.is_infradmin() || self.is_systemuser() { return Ok(true); }