diff --git a/crates/app/src/public/html/chats/app.lisp b/crates/app/src/public/html/chats/app.lisp
index 97bb440..e7cc4ec 100644
--- a/crates/app/src/public/html/chats/app.lisp
+++ b/crates/app/src/public/html/chats/app.lisp
@@ -536,7 +536,6 @@
method: \"Headers\",
data: JSON.stringify({
// SocketHeaders
- user: \"{{ user.id }}\",
is_channel: window.SUBSCRIBE_CHANNEL,
}),
}),
diff --git a/crates/app/src/public/html/chats/channels.lisp b/crates/app/src/public/html/chats/channels.lisp
index e8498b2..a87dbeb 100644
--- a/crates/app/src/public/html/chats/channels.lisp
+++ b/crates/app/src/public/html/chats/channels.lisp
@@ -44,7 +44,7 @@
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"general:action.delete\" }}")))
- (text "{% else %}")
+ (text "{% elif selected_community == 0 %}")
(button
("onclick" "kick_member('{{ channel.id }}', '{{ user.id }}')")
("class" "red")
diff --git a/crates/app/src/routes/api/v1/channels/channels.rs b/crates/app/src/routes/api/v1/channels/channels.rs
index 584cc78..e3ead5a 100644
--- a/crates/app/src/routes/api/v1/channels/channels.rs
+++ b/crates/app/src/routes/api/v1/channels/channels.rs
@@ -1,6 +1,6 @@
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
-use tetratto_core::model::{channels::Channel, ApiReturn, Error};
+use tetratto_core::model::{oauth, channels::Channel, ApiReturn, Error};
use crate::{
get_user_from_token,
routes::api::v1::{
@@ -15,7 +15,7 @@ pub async fn create_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityCreateChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -47,7 +47,7 @@ pub async fn create_group_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -111,7 +111,7 @@ pub async fn delete_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -133,7 +133,7 @@ pub async fn update_title_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -155,7 +155,7 @@ pub async fn update_position_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -177,7 +177,7 @@ pub async fn add_member_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -199,7 +199,7 @@ pub async fn kick_member_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -223,3 +223,73 @@ pub async fn kick_member_request(
Err(e) => Json(e.into()),
}
}
+
+pub async fn get_dm_channels_request(
+ jar: CookieJar,
+ Extension(data): Extension,
+) -> impl IntoResponse {
+ let data = &(data.read().await).0;
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
+ Some(ua) => ua,
+ None => return Json(Error::NotAllowed.into()),
+ };
+
+ match data.get_channels_by_user(user.id).await {
+ Ok(c) => Json(ApiReturn {
+ ok: true,
+ message: "Success".to_string(),
+ payload: Some(c),
+ }),
+ Err(e) => Json(e.into()),
+ }
+}
+
+pub async fn get_community_channels_request(
+ jar: CookieJar,
+ Extension(data): Extension,
+ Path(id): Path,
+) -> impl IntoResponse {
+ let data = &(data.read().await).0;
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels) {
+ Some(ua) => ua,
+ None => return Json(Error::NotAllowed.into()),
+ };
+
+ if data
+ .get_membership_by_owner_community_no_void(user.id, id)
+ .await
+ .is_err()
+ {
+ // must be a member of the community to request channels
+ return Json(Error::NotAllowed.into());
+ }
+
+ match data.get_channels_by_community(id).await {
+ Ok(c) => Json(ApiReturn {
+ ok: true,
+ message: "Success".to_string(),
+ payload: Some(c),
+ }),
+ Err(e) => Json(e.into()),
+ }
+}
+
+pub async fn get_request(
+ jar: CookieJar,
+ Extension(data): Extension,
+ Path(id): Path,
+) -> impl IntoResponse {
+ let data = &(data.read().await).0;
+ if get_user_from_token!(jar, data, oauth::AppScope::CommunityManageChannels).is_none() {
+ return Json(Error::NotAllowed.into());
+ }
+
+ match data.get_channel_by_id(id).await {
+ Ok(c) => Json(ApiReturn {
+ ok: true,
+ message: "Success".to_string(),
+ payload: Some(c),
+ }),
+ Err(e) => Json(e.into()),
+ }
+}
diff --git a/crates/app/src/routes/api/v1/channels/messages.rs b/crates/app/src/routes/api/v1/channels/messages.rs
index 1cee397..e88138e 100644
--- a/crates/app/src/routes/api/v1/channels/messages.rs
+++ b/crates/app/src/routes/api/v1/channels/messages.rs
@@ -2,15 +2,16 @@ use std::{collections::HashMap, time::Duration};
use axum::{
extract::{
ws::{Message as WsMessage, WebSocket, WebSocketUpgrade},
- Path,
+ Path, Query,
},
- response::{IntoResponse, Response},
+ response::IntoResponse,
Extension, Json,
};
use axum_extra::extract::CookieJar;
use tetratto_core::{
cache::{Cache, redis::Commands},
model::{
+ oauth,
auth::User,
channels::Message,
socket::{PacketType, SocketMessage, SocketMethod},
@@ -18,36 +19,42 @@ use tetratto_core::{
},
DataManager,
};
-use crate::{get_user_from_token, routes::api::v1::CreateMessage, State};
+use crate::{
+ get_user_from_token,
+ routes::{api::v1::CreateMessage, pages::PaginatedQuery},
+ State,
+};
use serde::Deserialize;
use futures_util::{sink::SinkExt, stream::StreamExt};
#[derive(Clone, Deserialize)]
pub struct SocketHeaders {
- pub user: String,
pub is_channel: bool,
}
/// Handle a subscription to the websocket.
pub async fn subscription_handler(
+ jar: CookieJar,
ws: WebSocketUpgrade,
Extension(data): Extension,
Path(id): Path,
-) -> Response {
- let data = &(data.read().await);
- let data = data.0.clone();
+) -> impl IntoResponse {
+ let data = &(data.read().await).0;
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadSockets) {
+ Some(ua) => ua,
+ None => return Err(Error::NotAllowed.to_string()),
+ };
- ws.on_upgrade(|socket| async move {
+ let data = data.clone();
+ Ok(ws.on_upgrade(|socket| async move {
tokio::spawn(async move {
- handle_socket(socket, data, id).await;
+ handle_socket(socket, data, id, user).await;
});
- })
+ }))
}
-pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: String) {
+pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: String, user: User) {
let (mut sink, mut stream) = socket.split();
-
- let mut user: Option = None;
let mut headers: Option = None;
let channel_id = format!("chats/{community_id}");
@@ -63,7 +70,7 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
};
- if data.method != SocketMethod::Headers && user.is_none() && headers.is_none() {
+ if data.method != SocketMethod::Headers && headers.is_none() {
// we've sent something else before authenticating... that's not right
let _ = sink.close().await;
return;
@@ -74,24 +81,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
let data: SocketHeaders = data.data();
headers = Some(data.clone());
- user = Some(
- match dbc
- .get_user_by_id(match data.user.parse::() {
- Ok(c) => c,
- Err(_) => {
- let _ = sink.close().await;
- return;
- }
- })
- .await
- {
- Ok(ua) => ua,
- Err(_) => {
- let _ = sink.close().await;
- return;
- }
- },
- );
if data.is_channel {
// verify permissions for single channel
@@ -112,8 +101,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
};
- let user = user.as_ref().unwrap();
-
let membership = match dbc
.get_membership_by_owner_community(user.id, channel.id)
.await
@@ -142,7 +129,6 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
// get channel permissions
- let user = user.unwrap();
let headers = headers.unwrap();
let mut channel_read_statuses: HashMap = HashMap::new();
@@ -280,7 +266,7 @@ pub async fn create_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateMessages) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -311,7 +297,7 @@ pub async fn delete_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserDeleteMessages) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -325,3 +311,42 @@ pub async fn delete_request(
Err(e) => Json(e.into()),
}
}
+
+pub async fn from_channel_request(
+ jar: CookieJar,
+ Extension(data): Extension,
+ Path(id): Path,
+ Query(props): Query,
+) -> impl IntoResponse {
+ let data = &(data.read().await).0;
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateMessages) {
+ Some(ua) => ua,
+ None => return Json(Error::NotAllowed.into()),
+ };
+
+ let channel = match data.get_channel_by_id(id).await {
+ Ok(c) => c,
+ Err(e) => return Json(e.into()),
+ };
+
+ let membership = match data
+ .get_membership_by_owner_community(user.id, channel.community)
+ .await
+ {
+ Ok(m) => m,
+ Err(e) => return Json(e.into()),
+ };
+
+ if !channel.check_read(user.id, Some(membership.role)) {
+ return Json(Error::NotAllowed.into());
+ }
+
+ match data.get_messages_by_channel(id, 24, props.page).await {
+ Ok(m) => Json(ApiReturn {
+ ok: true,
+ message: "Success".to_string(),
+ payload: Some(m),
+ }),
+ Err(e) => Json(e.into()),
+ }
+}
diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs
index b912c04..242dceb 100644
--- a/crates/app/src/routes/api/v1/mod.rs
+++ b/crates/app/src/routes/api/v1/mod.rs
@@ -433,6 +433,15 @@ pub fn routes() -> Router {
"/channels/{id}/kick",
post(channels::channels::kick_member_request),
)
+ .route("/channels/{id}", get(channels::channels::get_request))
+ .route(
+ "/channels/community/{id}",
+ get(channels::channels::get_community_channels_request),
+ )
+ .route(
+ "/channels/dms",
+ get(channels::channels::get_dm_channels_request),
+ )
// messages
.route(
"/_connect/{id}",
@@ -440,6 +449,10 @@ pub fn routes() -> Router {
)
.route("/messages", post(channels::messages::create_request))
.route("/messages/{id}", delete(channels::messages::delete_request))
+ .route(
+ "/messages/from_channel/{id}",
+ get(channels::messages::from_channel_request),
+ )
// emojis
.route(
"/lookup_emoji",
diff --git a/crates/app/src/routes/api/v1/reactions.rs b/crates/app/src/routes/api/v1/reactions.rs
index 3b3529a..b3efe52 100644
--- a/crates/app/src/routes/api/v1/reactions.rs
+++ b/crates/app/src/routes/api/v1/reactions.rs
@@ -1,7 +1,7 @@
use crate::{State, get_user_from_token, routes::api::v1::CreateReaction};
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
-use tetratto_core::model::{ApiReturn, Error, reactions::Reaction};
+use tetratto_core::model::{oauth, ApiReturn, Error, reactions::Reaction};
pub async fn get_request(
jar: CookieJar,
@@ -9,7 +9,7 @@ pub async fn get_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReact) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -30,7 +30,7 @@ pub async fn create_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReact) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -81,7 +81,7 @@ pub async fn delete_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReact) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
diff --git a/crates/app/src/routes/api/v1/stacks.rs b/crates/app/src/routes/api/v1/stacks.rs
index 85ebd5a..8faab1f 100644
--- a/crates/app/src/routes/api/v1/stacks.rs
+++ b/crates/app/src/routes/api/v1/stacks.rs
@@ -1,7 +1,7 @@
use crate::{State, get_user_from_token};
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
-use tetratto_core::model::{stacks::UserStack, ApiReturn, Error};
+use tetratto_core::model::{oauth, stacks::UserStack, ApiReturn, Error};
use super::{
AddOrRemoveStackUser, CreateStack, UpdateStackMode, UpdateStackName, UpdateStackPrivacy,
UpdateStackSort,
@@ -13,7 +13,7 @@ pub async fn create_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -38,7 +38,7 @@ pub async fn update_name_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -60,7 +60,7 @@ pub async fn update_privacy_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -82,7 +82,7 @@ pub async fn update_mode_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -104,7 +104,7 @@ pub async fn update_sort_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -126,7 +126,7 @@ pub async fn add_user_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -169,7 +169,7 @@ pub async fn remove_user_request(
Json(req): Json,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
@@ -207,7 +207,7 @@ pub async fn delete_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageStacks) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
diff --git a/crates/app/src/routes/api/v1/uploads.rs b/crates/app/src/routes/api/v1/uploads.rs
index 9d587c2..c90c427 100644
--- a/crates/app/src/routes/api/v1/uploads.rs
+++ b/crates/app/src/routes/api/v1/uploads.rs
@@ -4,7 +4,7 @@ use axum_extra::extract::CookieJar;
use pathbufd::PathBufD;
use crate::{get_user_from_token, State};
use super::auth::images::read_image;
-use tetratto_core::model::{ApiReturn, Error};
+use tetratto_core::model::{oauth, ApiReturn, Error};
pub async fn get_request(
Path(id): Path,
@@ -38,7 +38,7 @@ pub async fn delete_request(
Path(id): Path,
) -> impl IntoResponse {
let data = &(data.read().await).0;
- let user = match get_user_from_token!(jar, data) {
+ let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageUploads) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
diff --git a/crates/app/src/routes/pages/chats.rs b/crates/app/src/routes/pages/chats.rs
index 0c78039..e6ef791 100644
--- a/crates/app/src/routes/pages/chats.rs
+++ b/crates/app/src/routes/pages/chats.rs
@@ -159,6 +159,11 @@ pub async fn stream_request(
let ignore_users = crate::ignore_users_gen!(user!, data);
+ let channel = match data.0.get_channel_by_id(channel).await {
+ Ok(c) => c,
+ Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
+ };
+
let membership = match data
.0
.get_membership_by_owner_community(user.id, community)
@@ -168,13 +173,19 @@ pub async fn stream_request(
Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
};
+ if !channel.check_read(user.id, Some(membership.role)) {
+ return Err(Html(
+ render_error(Error::NotAllowed, &jar, &data, &Some(user)).await,
+ ));
+ }
+
let can_manage_messages = membership.role.check(CommunityPermission::MANAGE_MESSAGES)
| user.permissions.check(FinePermission::MANAGE_MESSAGES);
let messages = if props.message == 0 {
match data
.0
- .get_messages_by_channel(channel, 24, props.page)
+ .get_messages_by_channel(channel.id, 24, props.page)
.await
{
Ok(p) => match data.0.fill_messages(p, &ignore_users).await {
diff --git a/crates/core/src/model/oauth.rs b/crates/core/src/model/oauth.rs
index 006690f..6ac3e83 100644
--- a/crates/core/src/model/oauth.rs
+++ b/crates/core/src/model/oauth.rs
@@ -70,6 +70,8 @@ pub enum AppScope {
UserCreateDrafts,
/// Create communities on behalf of the user.
UserCreateCommunities,
+ /// Create stacks on behalf of the user.
+ UserCreateStacks,
/// Delete posts owned by the user.
UserDeletePosts,
/// Delete messages owned by the user.
@@ -98,12 +100,16 @@ pub enum AppScope {
UserManageNotifications,
/// Manage the user's requests.
UserManageRequests,
+ /// Manage the user's uploads.
+ UserManageUploads,
/// Edit posts created by the user.
UserEditPosts,
/// Edit drafts created by the user.
UserEditDrafts,
/// Vote in polls as the user.
UserVote,
+ /// React to posts on behalf of the user. Also allows the removal of reactions.
+ UserReact,
/// Join communities on behalf of the user.
UserJoinCommunities,
/// Permanently delete posts.
@@ -126,6 +132,10 @@ pub enum AppScope {
CommunityTransferOwnership,
/// Read the membership of users in communities owned by the current user.
CommunityReadMemberships,
+ /// Create channels in the user's communities.
+ CommunityCreateChannels,
+ /// Manage channels in the user's communities.
+ CommunityManageChannels,
}
impl AppScope {
@@ -164,9 +174,11 @@ impl AppScope {
"user-manage-blocks" => Self::UserManageBlocks,
"user-manage-notifications" => Self::UserManageNotifications,
"user-manage-requests" => Self::UserManageRequests,
+ "user-manage-uploads" => Self::UserManageUploads,
"user-edit-posts" => Self::UserEditPosts,
"user-edit-drafts" => Self::UserEditDrafts,
"user-vote" => Self::UserVote,
+ "user-react" => Self::UserReact,
"user-join-communities" => Self::UserJoinCommunities,
"mod-purge-posts" => Self::ModPurgePosts,
"mod-delete-posts" => Self::ModDeletePosts,
@@ -178,6 +190,8 @@ impl AppScope {
"community-manage" => Self::CommunityManage,
"community-transfer-ownership" => Self::CommunityTransferOwnership,
"community-read-memberships" => Self::CommunityReadMemberships,
+ "community-create-channels" => Self::CommunityCreateChannels,
+ "community-manage-channels" => Self::CommunityManageChannels,
_ => continue,
})
}