pinakes-core: add backup, session refresh, share permissions restructure, and fix integrity
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I17da1cf8403bd11d2a6ea31138f97e776a6a6964
This commit is contained in:
parent
672e11b592
commit
4e91cb6679
4 changed files with 3096 additions and 2300 deletions
|
|
@ -20,6 +20,7 @@ pub struct ShareId(pub Uuid);
|
|||
|
||||
impl ShareId {
|
||||
/// Creates a new share ID.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(Uuid::now_v7())
|
||||
}
|
||||
|
|
@ -49,7 +50,8 @@ pub enum ShareTarget {
|
|||
|
||||
impl ShareTarget {
|
||||
/// Returns the type of target being shared.
|
||||
pub fn target_type(&self) -> &'static str {
|
||||
#[must_use]
|
||||
pub const fn target_type(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Media { .. } => "media",
|
||||
Self::Collection { .. } => "collection",
|
||||
|
|
@ -59,7 +61,8 @@ impl ShareTarget {
|
|||
}
|
||||
|
||||
/// Returns the ID of the target being shared.
|
||||
pub fn target_id(&self) -> Uuid {
|
||||
#[must_use]
|
||||
pub const fn target_id(&self) -> Uuid {
|
||||
match self {
|
||||
Self::Media { media_id } => media_id.0,
|
||||
Self::Collection { collection_id } => *collection_id,
|
||||
|
|
@ -91,7 +94,8 @@ pub enum ShareRecipient {
|
|||
|
||||
impl ShareRecipient {
|
||||
/// Returns the type of recipient.
|
||||
pub fn recipient_type(&self) -> &'static str {
|
||||
#[must_use]
|
||||
pub const fn recipient_type(&self) -> &'static str {
|
||||
match self {
|
||||
Self::PublicLink { .. } => "public_link",
|
||||
Self::User { .. } => "user",
|
||||
|
|
@ -101,75 +105,117 @@ impl ShareRecipient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read-access permissions granted by a share.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct ShareViewPermissions {
|
||||
/// Can view the content
|
||||
pub can_view: bool,
|
||||
/// Can download the content
|
||||
pub can_download: bool,
|
||||
/// Can reshare with others
|
||||
pub can_reshare: bool,
|
||||
}
|
||||
|
||||
/// Write-access permissions granted by a share.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct ShareMutatePermissions {
|
||||
/// Can edit the content/metadata
|
||||
pub can_edit: bool,
|
||||
/// Can delete the content
|
||||
pub can_delete: bool,
|
||||
/// Can add new items (for collections)
|
||||
pub can_add: bool,
|
||||
}
|
||||
|
||||
/// Permissions granted by a share.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct SharePermissions {
|
||||
/// Can view the content
|
||||
pub can_view: bool,
|
||||
/// Can download the content
|
||||
pub can_download: bool,
|
||||
/// Can edit the content/metadata
|
||||
pub can_edit: bool,
|
||||
/// Can delete the content
|
||||
pub can_delete: bool,
|
||||
/// Can reshare with others
|
||||
pub can_reshare: bool,
|
||||
/// Can add new items (for collections)
|
||||
pub can_add: bool,
|
||||
#[serde(flatten)]
|
||||
pub view: ShareViewPermissions,
|
||||
#[serde(flatten)]
|
||||
pub mutate: ShareMutatePermissions,
|
||||
}
|
||||
|
||||
impl SharePermissions {
|
||||
/// Creates a new share with view-only permissions.
|
||||
#[must_use]
|
||||
pub fn view_only() -> Self {
|
||||
Self {
|
||||
can_view: true,
|
||||
..Default::default()
|
||||
view: ShareViewPermissions {
|
||||
can_view: true,
|
||||
..Default::default()
|
||||
},
|
||||
mutate: ShareMutatePermissions::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new share with download permissions.
|
||||
#[must_use]
|
||||
pub fn download() -> Self {
|
||||
Self {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
..Default::default()
|
||||
view: ShareViewPermissions {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
..Default::default()
|
||||
},
|
||||
mutate: ShareMutatePermissions::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new share with edit permissions.
|
||||
#[must_use]
|
||||
pub fn edit() -> Self {
|
||||
Self {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
can_edit: true,
|
||||
can_add: true,
|
||||
..Default::default()
|
||||
view: ShareViewPermissions {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
..Default::default()
|
||||
},
|
||||
mutate: ShareMutatePermissions {
|
||||
can_edit: true,
|
||||
can_add: true,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new share with full permissions.
|
||||
pub fn full() -> Self {
|
||||
#[must_use]
|
||||
pub const fn full() -> Self {
|
||||
Self {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
can_edit: true,
|
||||
can_delete: true,
|
||||
can_reshare: true,
|
||||
can_add: true,
|
||||
view: ShareViewPermissions {
|
||||
can_view: true,
|
||||
can_download: true,
|
||||
can_reshare: true,
|
||||
},
|
||||
mutate: ShareMutatePermissions {
|
||||
can_edit: true,
|
||||
can_delete: true,
|
||||
can_add: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges two permission sets, taking the most permissive values.
|
||||
pub fn merge(&self, other: &Self) -> Self {
|
||||
#[must_use]
|
||||
pub const fn merge(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
can_view: self.can_view || other.can_view,
|
||||
can_download: self.can_download || other.can_download,
|
||||
can_edit: self.can_edit || other.can_edit,
|
||||
can_delete: self.can_delete || other.can_delete,
|
||||
can_reshare: self.can_reshare || other.can_reshare,
|
||||
can_add: self.can_add || other.can_add,
|
||||
view: ShareViewPermissions {
|
||||
can_view: self.view.can_view || other.view.can_view,
|
||||
can_download: self.view.can_download || other.view.can_download,
|
||||
can_reshare: self.view.can_reshare || other.view.can_reshare,
|
||||
},
|
||||
mutate: ShareMutatePermissions {
|
||||
can_edit: self.mutate.can_edit || other.mutate.can_edit,
|
||||
can_delete: self.mutate.can_delete || other.mutate.can_delete,
|
||||
can_add: self.mutate.can_add || other.mutate.can_add,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -196,6 +242,7 @@ pub struct Share {
|
|||
|
||||
impl Share {
|
||||
/// Create a new public link share.
|
||||
#[must_use]
|
||||
pub fn new_public_link(
|
||||
owner_id: UserId,
|
||||
target: ShareTarget,
|
||||
|
|
@ -224,6 +271,7 @@ impl Share {
|
|||
}
|
||||
|
||||
/// Create a new user share.
|
||||
#[must_use]
|
||||
pub fn new_user_share(
|
||||
owner_id: UserId,
|
||||
target: ShareTarget,
|
||||
|
|
@ -251,16 +299,19 @@ impl Share {
|
|||
}
|
||||
|
||||
/// Checks if the share has expired.
|
||||
#[must_use]
|
||||
pub fn is_expired(&self) -> bool {
|
||||
self.expires_at.map(|exp| exp < Utc::now()).unwrap_or(false)
|
||||
self.expires_at.is_some_and(|exp| exp < Utc::now())
|
||||
}
|
||||
|
||||
/// Checks if this is a public link share.
|
||||
pub fn is_public(&self) -> bool {
|
||||
#[must_use]
|
||||
pub const fn is_public(&self) -> bool {
|
||||
matches!(self.recipient, ShareRecipient::PublicLink { .. })
|
||||
}
|
||||
|
||||
/// Returns the public token if this is a public link share.
|
||||
#[must_use]
|
||||
pub fn public_token(&self) -> Option<&str> {
|
||||
match &self.recipient {
|
||||
ShareRecipient::PublicLink { token, .. } => Some(token),
|
||||
|
|
@ -308,7 +359,7 @@ impl std::str::FromStr for ShareActivityAction {
|
|||
"revoked" => Ok(Self::Revoked),
|
||||
"expired" => Ok(Self::Expired),
|
||||
"password_failed" => Ok(Self::PasswordFailed),
|
||||
_ => Err(format!("unknown share activity action: {}", s)),
|
||||
_ => Err(format!("unknown share activity action: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -327,6 +378,7 @@ pub struct ShareActivity {
|
|||
|
||||
impl ShareActivity {
|
||||
/// Creates a new share activity entry.
|
||||
#[must_use]
|
||||
pub fn new(share_id: ShareId, action: ShareActivityAction) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
|
|
@ -340,18 +392,21 @@ impl ShareActivity {
|
|||
}
|
||||
|
||||
/// Sets the actor who performed the activity.
|
||||
pub fn with_actor(mut self, actor_id: UserId) -> Self {
|
||||
#[must_use]
|
||||
pub const fn with_actor(mut self, actor_id: UserId) -> Self {
|
||||
self.actor_id = Some(actor_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the IP address of the actor.
|
||||
#[must_use]
|
||||
pub fn with_ip(mut self, ip: &str) -> Self {
|
||||
self.actor_ip = Some(ip.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets additional details about the activity.
|
||||
#[must_use]
|
||||
pub fn with_details(mut self, details: &str) -> Self {
|
||||
self.details = Some(details.to_string());
|
||||
self
|
||||
|
|
@ -391,7 +446,7 @@ impl std::str::FromStr for ShareNotificationType {
|
|||
"share_revoked" => Ok(Self::ShareRevoked),
|
||||
"share_expiring" => Ok(Self::ShareExpiring),
|
||||
"share_accessed" => Ok(Self::ShareAccessed),
|
||||
_ => Err(format!("unknown share notification type: {}", s)),
|
||||
_ => Err(format!("unknown share notification type: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -409,6 +464,7 @@ pub struct ShareNotification {
|
|||
|
||||
impl ShareNotification {
|
||||
/// Creates a new share notification.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
user_id: UserId,
|
||||
share_id: ShareId,
|
||||
|
|
@ -426,17 +482,23 @@ impl ShareNotification {
|
|||
}
|
||||
|
||||
/// Generates a random share token.
|
||||
#[must_use]
|
||||
pub fn generate_share_token() -> String {
|
||||
// Use UUIDv4 for random tokens - simple string representation
|
||||
Uuid::new_v4().simple().to_string()
|
||||
}
|
||||
|
||||
/// Hashes a share password using Argon2id.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if hashing fails.
|
||||
pub fn hash_share_password(password: &str) -> Result<String, PinakesError> {
|
||||
crate::users::auth::hash_password(password)
|
||||
}
|
||||
|
||||
/// Verifies a share password against an Argon2id hash.
|
||||
#[must_use]
|
||||
pub fn verify_share_password(password: &str, hash: &str) -> bool {
|
||||
crate::users::auth::verify_password(password, hash).unwrap_or(false)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue