Started documenting rust.

This commit is contained in:
Johannes Randerath
2024-08-30 11:02:32 +02:00
parent 0f6e2e3d99
commit d422a4b31c
10 changed files with 127 additions and 12 deletions

View File

@@ -15,6 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Module responsible for handling user authentication
use crate::Db;
use rocket::response::Redirect;
@@ -25,9 +27,10 @@ use rocket_oauth2::{OAuth2, TokenResponse};
use reqwest::Client;
use rocket::serde::{Deserialize, json::Json};
/// OAuth provider
pub struct RanderathIdentity;
/// User attempting to log in. Username and email provided by the oauth provider.
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct UnAuthUser {
@@ -35,12 +38,15 @@ struct UnAuthUser {
email: String,
}
/// User currently logged in and authenticated.
pub struct AuthUser {
pub uid: i32,
pub uname: String,
pub email: String,
}
/// Using the client browser's private cookies, check if the user is logged in.
/// If yes, calculate the corresponding AuthUser, else Forward to Unauthorized.
#[rocket::async_trait]
impl<'r> request::FromRequest<'r> for AuthUser {
type Error = ();
@@ -63,6 +69,8 @@ impl<'r> request::FromRequest<'r> for AuthUser {
}
}
/// Utility function invoked when trying to log in to fetch user data from the database, given an oauth access token.
/// Fetch user info from oauth provider using the access token, calculate the UnAuthUser from the Json response, query the database and return the user if it exists or create it if it doesn't.
pub async fn login_or_register(conn: Db, access_token: &str) -> Option<inventur_db::User> {
let ui = Client::new()
.get("https://ldap.randerath.eu/realms/master/protocol/openid-connect/userinfo")
@@ -85,16 +93,26 @@ pub async fn login_or_register(conn: Db, access_token: &str) -> Option<inventur_
}
/// Unauthorized requests are sent to the oauth provider in order for the user to authenticate.
#[catch(401)]
pub async fn redirect_to_login_401() -> Redirect {
Redirect::to(uri!(oauth_login))
}
/// Unauthorized requests are sent to the oauth provider in order for the user to authenticate.
#[catch(403)]
pub async fn redirect_to_login() -> Redirect {
Redirect::to(uri!(oauth_login()))
}
/// Redirect to oauth provider, fetching the correct link.
#[get("/login")]
pub fn oauth_login(oauth2: OAuth2<RanderathIdentity>, cookies: &CookieJar<'_>) -> Redirect {
oauth2.get_redirect(cookies, &["openid"]).unwrap()
}
/// Callback called by the oauth provider
/// Process TokenResponse, set login cookies and log the user in.
#[get("/auth")]
pub async fn oauth_callback(conn: Db, token: TokenResponse<RanderathIdentity>, cookies: &CookieJar<'_>) -> Result<Redirect, Status> {
let at = token.access_token().to_string();

View File

@@ -15,10 +15,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Rocket app to set up a platform of cloud-stored tables for keeping track of data records.
//! Basically inventur tries to combine the ease of use of spreadsheets with the entry-based system of databases.
//! Oauth2 is used as the authentication system, diesel mysql and the subcrate inventur_db are used for database handling.
#[macro_use] extern crate rocket;
/// Views, utility methods and data structures related to user authentication
mod auth;
/// Views, utility mehtods and data structures, related to displaying and modifying tables (everything the user sees under /table).
mod table;
/// Data structure representing an authenticated, logged in user.
use auth::AuthUser;
use rocket::fs::{FileServer, relative};
@@ -34,10 +41,12 @@ use dotenvy::dotenv;
use rocket::Config;
use rocket::figment::providers::{Toml, Env, Format};
/// Database connection using diesel and rocket_sync_db_pools
#[database("inventur")]
pub struct Db(diesel::MysqlConnection);
/// Home page featuring a preview view of all the user's tables
/// Needs an authenticated user.
#[get("/")]
async fn home(conn: Db, user: AuthUser) -> Template {
let uid = user.uid;
@@ -63,11 +72,13 @@ async fn home(conn: Db, user: AuthUser) -> Template {
)
}
/// If no user is authenticated, redirect the user to authenticate with the oauth identity provider.
#[get("/", rank=2)]
async fn login_home() -> Redirect {
Redirect::to(uri!(auth::oauth_login()))
}
/// Serve the favicon as if it were in /favicon.ico
#[get("/favicon.ico")]
async fn favicon() -> Redirect {
Redirect::to(uri!("/img/favicon.ico"))
@@ -75,6 +86,7 @@ async fn favicon() -> Redirect {
#[launch]
async fn rocket() -> _ {
/// Load configuration from a file called .env in the project's root.
dotenv().ok();
let cfg = Config::figment()
@@ -82,14 +94,23 @@ async fn rocket() -> _ {
rocket::custom(cfg)
/// Use tera templates
.attach(Template::fairing())
/// Connect to mysql db
.attach(Db::fairing())
/// Set up oauth
.attach(OAuth2::<auth::RanderathIdentity>::fairing("oauth"))
/// Everything related to the home page and login
.mount("/", routes![auth::oauth_login, auth::oauth_callback, home, login_home, favicon])
/// Everything related to the table view and modifying the table as an object (as opposed to its rows and columns).
.mount("/table", routes![table::table, table::table_sec, table::edit_tname, table::create_table, table::import_table, table::delete_table])
/// Modify table rows and their contents
.mount("/row", routes![table::new_entry, table::edit_entry, table::delete_entry])
/// Modify the table's columns, their names and types.
.mount("/column", routes![table::delete_column, table::edit_column])
/// If not logged in, redirect to oauth login
.register("/", catchers![auth::redirect_to_login])
/// Serve static files in the corresponding subdirs of /static.
.mount("/img", FileServer::from(relative!("static/img")))
.mount("/css", FileServer::from(relative!("static/css")))
.mount("/js", FileServer::from(relative!("static/js")))

View File

@@ -16,10 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Module responsible for handling display, query and modification of tables, rows, columns and cells.
/// Everything related to displaying the tables
pub mod table_view;
/// Everything related to manipulating table objects (as opposed to its member).
pub mod table_manipulate_table;
/// Everything related to manipulating table columns and their types.
pub mod table_manipulate_column;
/// Everything related to manipulating table rows and cells.
pub mod table_manipulate_entry;
/// Structs used to handle form data for manipulating the database.
pub mod forms;
pub use self::table_view::*;

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Submodule holding structs relevant to handle form data.
use rocket::form::Form;
use rocket::serde::{Serialize, Deserialize};
@@ -59,6 +61,7 @@ pub struct DeleteEntry {
pub row_pos: i32,
}
/// Edit a table's name
#[derive(FromForm)]
pub struct EditTname {
pub tblid: i32,
@@ -85,8 +88,4 @@ pub struct DeleteTable {
pub tblid: i32,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct JRows {
pub rows: Vec<Vec<String>>,
}

View File

@@ -16,6 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Submodule responsible for manipulating and creating a table's column.
//! For all methods in this module, the authenticated user must be the table's owner.
use crate::auth;
use crate::Db;
use crate::table::forms;
@@ -33,6 +36,7 @@ use auth::AuthUser;
use self::forms::{NewColumn, EditColumn, DeleteColumn};
use self::table_view::rocket_uri_macro_table_sec;
/// Add a new column to an existing table
#[post("/create", data="<data>")]
pub async fn create_column(conn: Db, data: Form<NewColumn>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -44,6 +48,7 @@ pub async fn create_column(conn: Db, data: Form<NewColumn>, user: AuthUser) -> R
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
/// Edit a column (its name and type)
#[post("/edit", data="<data>")]
pub async fn edit_column(conn: Db, data: Form<EditColumn>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -56,6 +61,7 @@ pub async fn edit_column(conn: Db, data: Form<EditColumn>, user: AuthUser) -> Re
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
/// Delete a column
#[post("/delete", data="<data>")]
pub async fn delete_column(conn: Db, data: Form<DeleteColumn>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;

View File

@@ -16,6 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Submodule responsible for manipulating and creating rows and cells to existing tables
//! For all methods in this module, the authenticated user has to be the table's owner
use crate::auth;
use crate::Db;
use crate::table::forms;
@@ -32,6 +35,7 @@ use auth::AuthUser;
use self::forms::{NewEntry, EditEntry, DeleteEntry};
use self::table_view::rocket_uri_macro_table_sec;
/// Create a new a new row to an existing table, filling all its columns with the supplied values.
#[post("/new", data="<data>")]
pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -43,6 +47,7 @@ pub async fn new_entry(conn: Db, data: Form<NewEntry>, user: AuthUser) -> Result
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
/// Edit a row's cell contents, the row being identified by its table and its position within the table.
#[post("/edit", data="<data>")]
pub async fn edit_entry(conn: Db, data: Form<EditEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -55,6 +60,7 @@ pub async fn edit_entry(conn: Db, data: Form<EditEntry>, user: AuthUser) -> Resu
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
/// Delete a row identified by its table and its position within the table.
#[post("/delete", data="<data>")]
pub async fn delete_entry(conn: Db, data: Form<DeleteEntry>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Submodule responsible for manipulating or creating table objects (not individual rows, columns or entries).
use crate::auth;
use crate::Db;
use crate::table::forms;
@@ -34,6 +36,8 @@ use inventur_db::FIELDTYPE;
use self::forms::{NewTable, DeleteTable, ImportTable, EditTname};
use self::table_view::rocket_uri_macro_table_sec;
/// Delete a table.
/// The authenticated user must be the table's owner (ensured by inventur_db).
#[post("/delete", data="<data>")]
pub async fn delete_table(conn: Db, data: Form<DeleteTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -44,6 +48,7 @@ pub async fn delete_table(conn: Db, data: Form<DeleteTable>, user: AuthUser) ->
Ok(Redirect::to(uri!("/")))
}
/// Create a new table with the given fields and name, make the authenticated user the new table's owner.
#[post("/create", data="<data>")]
pub async fn create_table(conn: Db, data: Form<NewTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -54,6 +59,8 @@ pub async fn create_table(conn: Db, data: Form<NewTable>, user: AuthUser) -> Res
Ok(Redirect::to(uri!("/table", table_sec(tblid.unwrap()))))
}
/// Create a new table including rows by importing a csv file.
/// Table's column types default to text.
#[post("/import", data="<data>")]
pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;
@@ -70,6 +77,7 @@ pub async fn import_table(conn: Db, data: Json<ImportTable>, user: AuthUser) ->
Ok(Redirect::to(uri!("/table", table_sec(tblid))))
}
/// Edit a table object (the table's name).
#[post("/name/edit", data="<data>")]
pub async fn edit_tname(conn: Db, data: Form<EditTname>, user: AuthUser) -> Result<Redirect, Status> {
let uid = user.uid;

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Submodule responsible for handling fetching and displaying a table on the /table view.
use crate::auth;
use crate::Db;
use inventur_db;
@@ -24,6 +26,9 @@ use auth::AuthUser;
use rocket_dyn_templates::{Template, context};
use rocket::response::Redirect;
/// View an authenticated user sees visiting the /table/<table id> page
/// Optional get parametes are the field to sort by, the direction of the sort, the fields to search in and the value to search for.
/// Returns None if the table does not exist, could not be fetched or if the user is not the table's owner.
#[get("/<tid>?<sort_dir>&<sort_field>&<search_fields>&<search_value>")]
pub async fn table(conn: Db, tid: i32, sort_dir: Option<u8>, sort_field: Option<usize>, search_fields: Option<Vec<i32>>, search_value: Option<String>, user: AuthUser) -> Option<Template> {
let uid = user.uid;
@@ -91,6 +96,8 @@ pub async fn table(conn: Db, tid: i32, sort_dir: Option<u8>, sort_field: Option<
)
}
/// View to redirect a post request handled to manipulate a table or its display representation back to the (new) table view.
/// Also uses table() but nulls all optional fields.
#[get("/<tid>", rank=2)]
pub async fn table_sec(conn: Db, tid: i32, user: AuthUser) -> Redirect {
let nus : Option<usize> = None;
@@ -100,6 +107,7 @@ pub async fn table_sec(conn: Db, tid: i32, user: AuthUser) -> Redirect {
Redirect::to(uri!("/table", table(tid, nu8, nus, nvi32, ns)))
}
/// Utility function to get all a user's tables as their ids.
pub async fn get_tids(conn: &Db, uid: i32) -> (Vec<i32>, Vec<String>) {
let mut tids = conn.run(move |c| inventur_db::get_user_tblids(c, uid)).await;
let tnames;