diff --git a/crates/app/src/public/html/journals/app.lisp b/crates/app/src/public/html/journals/app.lisp index 98779b7..39c6699 100644 --- a/crates/app/src/public/html/journals/app.lisp +++ b/crates/app/src/public/html/journals/app.lisp @@ -393,10 +393,6 @@ (script ("id" "editor_content") ("type" "text/markdown") (text "{{ note.content|remove_script_tags|safe }}")) (script (text "setTimeout(async () => { - if (!document.getElementById(\"preview_tab\").shadowRoot) { - document.getElementById(\"preview_tab\").attachShadow({ mode: \"open\" }); - } - document.getElementById(\"editor_tab\").innerHTML = \"\"; globalThis.editor = CodeMirror(document.getElementById(\"editor_tab\"), { value: document.getElementById(\"editor_content\").innerHTML, @@ -451,8 +447,7 @@ ).text(); const preview_token = window.crypto.randomUUID(); - document.getElementById(\"preview_tab\").shadowRoot.innerHTML = `${res}`; trigger(\"atto::hooks::tabs:switch\", [\"preview\"]); diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index 7117f69..b7bdd77 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -3,6 +3,7 @@ use oiseau::cache::Cache; use crate::model::auth::UserConnections; use crate::model::moderation::AuditLogEntry; use crate::model::oauth::AuthGrant; +use crate::model::permissions::SecondaryPermission; use crate::model::{ Error, Result, auth::{Token, User, UserSettings}, @@ -46,6 +47,7 @@ impl DataManager { grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(), associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(), invite_code: get!(x->21(i64)) as usize, + secondary_permissions: SecondaryPermission::from_bits(get!(x->7(i32)) as u32).unwrap(), } } @@ -224,7 +226,8 @@ impl DataManager { &"", &serde_json::to_string(&data.grants).unwrap(), &serde_json::to_string(&data.associated).unwrap(), - &(data.invite_code as i64) + &(data.invite_code as i64), + &(SecondaryPermission::DEFAULT.bits() as i32), ] ); diff --git a/crates/core/src/database/drivers/sql/create_users.sql b/crates/core/src/database/drivers/sql/create_users.sql index fa95be5..467d00a 100644 --- a/crates/core/src/database/drivers/sql/create_users.sql +++ b/crates/core/src/database/drivers/sql/create_users.sql @@ -19,5 +19,6 @@ CREATE TABLE IF NOT EXISTS users ( connections TEXT NOT NULL, stripe_id TEXT NOT NULL, grants TEXT NOT NULL, - associated TEXT NOT NULL + associated TEXT NOT NULL, + secondary_permissions INT NOT NULL ) diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index f1ed465..64ebc62 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use super::{oauth::AuthGrant, permissions::FinePermission}; +use super::{ + oauth::AuthGrant, + permissions::{FinePermission, SecondaryPermission}, +}; use serde::{Deserialize, Serialize}; use totp_rs::TOTP; use tetratto_shared::{ @@ -52,6 +55,9 @@ pub struct User { /// The ID of the [`InviteCode`] this user provided during registration. #[serde(default)] pub invite_code: usize, + /// Secondary permissions because the regular permissions struct ran out of possible bits. + #[serde(default)] + pub secondary_permissions: SecondaryPermission, } pub type UserConnections = @@ -287,6 +293,7 @@ impl User { grants: Vec::new(), associated: Vec::new(), invite_code: 0, + secondary_permissions: SecondaryPermission::DEFAULT, } } diff --git a/crates/core/src/model/permissions.rs b/crates/core/src/model/permissions.rs index 9cd6dcb..1584083 100644 --- a/crates/core/src/model/permissions.rs +++ b/crates/core/src/model/permissions.rs @@ -44,86 +44,110 @@ bitflags! { } } -impl Serialize for FinePermission { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_u32(self.bits()) - } +macro_rules! user_permission { + ($struct:ident, $visitor:ident, $banned_check:ident) => { + impl Serialize for $struct { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u32(self.bits()) + } + } + + struct $visitor; + impl Visitor<'_> for $visitor { + type Value = $struct; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("u32") + } + + fn visit_u32(self, value: u32) -> Result + where + E: DeError, + { + if let Some(permission) = $struct::from_bits(value) { + Ok(permission) + } else { + Ok($struct::from_bits_retain(value)) + } + } + + fn visit_i32(self, value: i32) -> Result + where + E: DeError, + { + if let Some(permission) = $struct::from_bits(value as u32) { + Ok(permission) + } else { + Ok($struct::from_bits_retain(value as u32)) + } + } + + fn visit_u64(self, value: u64) -> Result + where + E: DeError, + { + if let Some(permission) = $struct::from_bits(value as u32) { + Ok(permission) + } else { + Ok($struct::from_bits_retain(value as u32)) + } + } + } + + impl<'de> Deserialize<'de> for $struct { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any($visitor) + } + } + + impl $struct { + /// Join two permissions into a single `u32`. + pub fn join(lhs: $struct, rhs: $struct) -> Self { + lhs | rhs + } + + /// Check if the given `input` contains the given permission. + pub fn check(self, permission: $struct) -> bool { + if (self & $struct::ADMINISTRATOR) == $struct::ADMINISTRATOR { + // has administrator permission, meaning everything else is automatically true + return true; + } else if self.$banned_check() { + // has banned permission, meaning everything else is automatically false + return false; + } + + (self & permission) == permission + } + + /// Sink for checking if the permission is banned. + pub fn sink(&self) -> bool { + false + } + } + + impl Default for $struct { + fn default() -> Self { + Self::DEFAULT + } + } + }; } -struct FinePermissionVisitor; -impl Visitor<'_> for FinePermissionVisitor { - type Value = FinePermission; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("u32") - } - - fn visit_u32(self, value: u32) -> Result - where - E: DeError, - { - if let Some(permission) = FinePermission::from_bits(value) { - Ok(permission) - } else { - Ok(FinePermission::from_bits_retain(value)) - } - } - - fn visit_i32(self, value: i32) -> Result - where - E: DeError, - { - if let Some(permission) = FinePermission::from_bits(value as u32) { - Ok(permission) - } else { - Ok(FinePermission::from_bits_retain(value as u32)) - } - } - - fn visit_u64(self, value: u64) -> Result - where - E: DeError, - { - if let Some(permission) = FinePermission::from_bits(value as u32) { - Ok(permission) - } else { - Ok(FinePermission::from_bits_retain(value as u32)) - } - } -} - -impl<'de> Deserialize<'de> for FinePermission { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_any(FinePermissionVisitor) - } -} +user_permission!(FinePermission, FinePermissionVisitor, check_banned); impl FinePermission { - /// Join two [`FinePermission`]s into a single `u32`. - pub fn join(lhs: FinePermission, rhs: FinePermission) -> FinePermission { - lhs | rhs + /// Check if the given permission qualifies as "Banned" status. + pub fn check_banned(self) -> bool { + (self & FinePermission::BANNED) == FinePermission::BANNED } - /// Check if the given `input` contains the given [`FinePermission`]. - pub fn check(self, permission: FinePermission) -> bool { - if (self & FinePermission::ADMINISTRATOR) == FinePermission::ADMINISTRATOR { - // has administrator permission, meaning everything else is automatically true - return true; - } else if self.check_banned() { - // has banned permission, meaning everything else is automatically false - return false; - } - - (self & permission) == permission - } - - /// Check if the given [`FinePermission`] qualifies as "Helper" status. + /// Check if the given permission qualifies as "Helper" status. pub fn check_helper(self) -> bool { self.check(FinePermission::MANAGE_COMMUNITIES) && self.check(FinePermission::MANAGE_POSTS) @@ -133,24 +157,26 @@ impl FinePermission { && self.check(FinePermission::VIEW_AUDIT_LOG) } - /// Check if the given [`FinePermission`] qualifies as "Manager" status. + /// Check if the given permission qualifies as "Manager" status. pub fn check_manager(self) -> bool { self.check_helper() && self.check(FinePermission::MANAGE_USERS) } - /// Check if the given [`FinePermission`] qualifies as "Administrator" status. + /// Check if the given permission qualifies as "Administrator" status. pub fn check_admin(self) -> bool { self.check_manager() && self.check(FinePermission::ADMINISTRATOR) } +} - /// Check if the given [`FinePermission`] qualifies as "Banned" status. - pub fn check_banned(self) -> bool { - (self & FinePermission::BANNED) == FinePermission::BANNED +bitflags! { + /// Fine-grained permissions built using bitwise operations. Second permission value. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct SecondaryPermission: u32 { + const DEFAULT = 1 << 0; + const ADMINISTRATOR = 1 << 1; + + const _ = !0; } } -impl Default for FinePermission { - fn default() -> Self { - Self::DEFAULT - } -} +user_permission!(SecondaryPermission, SecondaryPermissionVisitor, sink); diff --git a/crates/shared/src/markdown.rs b/crates/shared/src/markdown.rs index c4b51da..1ae665b 100644 --- a/crates/shared/src/markdown.rs +++ b/crates/shared/src/markdown.rs @@ -14,8 +14,9 @@ pub fn render_markdown(input: &str) -> String { }, parse: ParseOptions { constructs: Constructs { - gfm_autolink_literal: true, - ..Default::default() + math_flow: true, + math_text: true, + ..Constructs::gfm() }, gfm_strikethrough_single_tilde: false, math_text_single_dollar: false,