feat: add access control to channels with various modes

- Introduced AccessMode enum to define channel access levels: Public, PasswordProtected, AccountRequired, and OwnerOnly.
- Updated Channel and ProgrammingBlock entities to include access_mode and access_password_hash fields.
- Enhanced create and update channel functionality to handle access mode and password.
- Implemented access checks in channel routes based on the defined access modes.
- Modified frontend components to support channel creation and editing with access control options.
- Added ChannelPasswordModal for handling password input when accessing restricted channels.
- Updated API calls to include channel and block passwords as needed.
- Created database migrations to add access_mode and access_password_hash columns to channels table.
This commit is contained in:
2026-03-14 01:45:10 +01:00
parent 924e162563
commit 81df6eb8ff
25 changed files with 635 additions and 53 deletions

View File

@@ -8,6 +8,8 @@ use axum::{Router, routing::{get, post}};
use chrono::{DateTime, Utc};
use uuid::Uuid;
use domain::{AccessMode, User};
use crate::{error::ApiError, state::AppState};
mod broadcast;
@@ -42,6 +44,40 @@ pub(super) fn require_owner(channel: &domain::Channel, user_id: Uuid) -> Result<
}
}
/// Gate access to a channel or block based on its `AccessMode`.
pub(super) fn check_access(
mode: &AccessMode,
password_hash: Option<&str>,
user: Option<&User>,
owner_id: Uuid,
supplied_password: Option<&str>,
) -> Result<(), ApiError> {
match mode {
AccessMode::Public => Ok(()),
AccessMode::PasswordProtected => {
let hash = password_hash.ok_or(ApiError::PasswordRequired)?;
let supplied = supplied_password.unwrap_or("").trim();
if supplied.is_empty() {
return Err(ApiError::PasswordRequired);
}
if !infra::auth::verify_password(supplied, hash) {
return Err(ApiError::PasswordRequired);
}
Ok(())
}
AccessMode::AccountRequired => {
if user.is_some() { Ok(()) } else { Err(ApiError::AuthRequired) }
}
AccessMode::OwnerOnly => {
if user.map(|u| u.id) == Some(owner_id) {
Ok(())
} else {
Err(ApiError::Forbidden("owner only".into()))
}
}
}
}
pub(super) fn parse_optional_dt(
s: Option<String>,
default: DateTime<Utc>,