mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 00:31:09 +00:00
Allow multiple initial members on room creation
This commit is contained in:
parent
cf5d648315
commit
ff89d36ee5
3 changed files with 87 additions and 21 deletions
|
@ -4,7 +4,10 @@ use std::{fs, io};
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
use bitflags::Flags;
|
||||
use blah::types::{ChatPayload, CreateRoomPayload, RoomAttrs, ServerPermission, UserKey, WithSig};
|
||||
use blah::types::{
|
||||
ChatPayload, CreateRoomPayload, MemberPermission, RoomAttrs, RoomMember, RoomMemberList,
|
||||
ServerPermission, UserKey, WithSig,
|
||||
};
|
||||
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
|
||||
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH};
|
||||
|
@ -208,8 +211,14 @@ async fn main_api(api_url: Url, command: ApiCommand) -> Result<()> {
|
|||
} => {
|
||||
let key = load_signing_key(&private_key_file)?;
|
||||
let payload = CreateRoomPayload {
|
||||
title,
|
||||
attrs: attrs.unwrap_or_default(),
|
||||
title,
|
||||
// The CLI does not support passing multiple members because `User` itself is a
|
||||
// disjoint arg-group.
|
||||
members: RoomMemberList(vec![RoomMember {
|
||||
permission: MemberPermission::ALL,
|
||||
user: UserKey(key.verifying_key().to_bytes()),
|
||||
}]),
|
||||
};
|
||||
let payload = WithSig::sign(&key, &mut OsRng, payload)?;
|
||||
|
||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -12,7 +12,7 @@ use axum::response::{sse, IntoResponse, Response};
|
|||
use axum::routing::{get, post};
|
||||
use axum::{async_trait, Json, Router};
|
||||
use blah::types::{
|
||||
AuthPayload, ChatItem, ChatPayload, CreateRoomPayload, RoomAttrs, RoomPermission,
|
||||
AuthPayload, ChatItem, ChatPayload, CreateRoomPayload, MemberPermission, RoomAttrs,
|
||||
ServerPermission, Signee, UserKey, WithSig,
|
||||
};
|
||||
use ed25519_dalek::SIGNATURE_LENGTH;
|
||||
|
@ -128,24 +128,30 @@ async fn room_create(
|
|||
st: ArcState,
|
||||
SignedJson(params): SignedJson<CreateRoomPayload>,
|
||||
) -> Result<Json<Uuid>, StatusCode> {
|
||||
let members = ¶ms.signee.payload.members.0;
|
||||
if !members
|
||||
.iter()
|
||||
.any(|m| m.user == params.signee.user && m.permission == MemberPermission::ALL)
|
||||
{
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let mut conn = st.conn.lock().unwrap();
|
||||
let Some((uid, _perm)) = conn
|
||||
let Some(true) = conn
|
||||
.query_row(
|
||||
r"
|
||||
SELECT `uid`, `permission`
|
||||
SELECT `permission`
|
||||
FROM `user`
|
||||
WHERE `userkey` = ?
|
||||
",
|
||||
params![params.signee.user],
|
||||
|row| {
|
||||
let uid = row.get::<_, u64>("uid")?;
|
||||
let perm = row.get::<_, ServerPermission>("permission")?;
|
||||
Ok((uid, perm))
|
||||
Ok(perm.contains(ServerPermission::CREATE_ROOM))
|
||||
},
|
||||
)
|
||||
.optional()
|
||||
.map_err(from_db_error)?
|
||||
.filter(|(_, perm)| perm.contains(ServerPermission::CREATE_ROOM))
|
||||
else {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
};
|
||||
|
@ -166,17 +172,31 @@ async fn room_create(
|
|||
},
|
||||
|row| row.get::<_, u64>(0),
|
||||
)?;
|
||||
txn.execute(
|
||||
let mut insert_user = txn.prepare(
|
||||
r"
|
||||
INSERT INTO `user` (`userkey`)
|
||||
VALUES (?)
|
||||
ON CONFLICT (`userkey`) DO NOTHING
|
||||
",
|
||||
)?;
|
||||
let mut insert_member = txn.prepare(
|
||||
r"
|
||||
INSERT INTO `room_member` (`rid`, `uid`, `permission`)
|
||||
VALUES (:rid, :uid, :permission)
|
||||
SELECT :rid, `uid`, :permission
|
||||
FROM `user`
|
||||
WHERE `userkey` = :userkey
|
||||
",
|
||||
named_params! {
|
||||
":rid": rid,
|
||||
":uid": uid,
|
||||
":permission": RoomPermission::ALL,
|
||||
},
|
||||
)?;
|
||||
for member in members {
|
||||
insert_user.execute(params![member.user])?;
|
||||
insert_member.execute(named_params! {
|
||||
":rid": rid,
|
||||
":userkey": member.user,
|
||||
":permission": member.permission,
|
||||
})?;
|
||||
}
|
||||
drop(insert_member);
|
||||
drop(insert_user);
|
||||
txn.commit()?;
|
||||
Ok(())
|
||||
})()
|
||||
|
@ -458,7 +478,7 @@ async fn room_post_item(
|
|||
named_params! {
|
||||
":ruuid": ruuid,
|
||||
":userkey": &chat.signee.user,
|
||||
":perm": RoomPermission::POST_CHAT,
|
||||
":perm": MemberPermission::POST_CHAT,
|
||||
},
|
||||
|row| Ok((row.get::<_, u64>("rid")?, row.get::<_, u64>("uid")?)),
|
||||
)
|
||||
|
|
47
src/types.rs
47
src/types.rs
|
@ -17,7 +17,7 @@ use uuid::Uuid;
|
|||
|
||||
const TIMESTAMP_TOLERENCE: u64 = 90;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct UserKey(#[serde(with = "hex::serde")] pub [u8; PUBLIC_KEY_LENGTH]);
|
||||
|
||||
|
@ -93,9 +93,46 @@ pub type ChatItem = WithSig<ChatPayload>;
|
|||
#[serde(tag = "typ", rename = "create_room")]
|
||||
pub struct CreateRoomPayload {
|
||||
pub attrs: RoomAttrs,
|
||||
/// The initial member list. Besides invariants of `RoomMemberList`, this also must include the
|
||||
/// room creator themselves, with the highest permission (-1).
|
||||
pub members: RoomMemberList,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// A collection of room members, with these invariants:
|
||||
/// 1. Sorted by userkeys.
|
||||
/// 2. No duplicated users.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(try_from = "Vec<RoomMember>")]
|
||||
pub struct RoomMemberList(pub Vec<RoomMember>);
|
||||
|
||||
impl Serialize for RoomMemberList {
|
||||
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(ser)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<RoomMember>> for RoomMemberList {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(members: Vec<RoomMember>) -> Result<Self, Self::Error> {
|
||||
if members.windows(2).all(|w| w[0].user.0 < w[1].user.0) {
|
||||
Ok(Self(members))
|
||||
} else {
|
||||
Err("unsorted or duplicated users")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RoomMember {
|
||||
pub permission: MemberPermission,
|
||||
pub user: UserKey,
|
||||
}
|
||||
|
||||
/// Proof of room membership for read-access.
|
||||
///
|
||||
/// TODO: Should we use JWT here instead?
|
||||
|
@ -107,7 +144,7 @@ pub struct AuthPayload {}
|
|||
#[serde(deny_unknown_fields, tag = "typ", rename_all = "snake_case")]
|
||||
pub enum RoomAdminPayload {
|
||||
AddMember {
|
||||
permission: RoomPermission,
|
||||
permission: MemberPermission,
|
||||
room: Uuid,
|
||||
user: UserKey,
|
||||
},
|
||||
|
@ -123,7 +160,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RoomPermission: u64 {
|
||||
pub struct MemberPermission: u64 {
|
||||
const POST_CHAT = 1 << 0;
|
||||
const ADD_MEMBER = 1 << 1;
|
||||
|
||||
|
@ -139,7 +176,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
impl_serde_for_bitflags!(ServerPermission);
|
||||
impl_serde_for_bitflags!(RoomPermission);
|
||||
impl_serde_for_bitflags!(MemberPermission);
|
||||
impl_serde_for_bitflags!(RoomAttrs);
|
||||
|
||||
mod sql_impl {
|
||||
|
@ -184,7 +221,7 @@ mod sql_impl {
|
|||
};
|
||||
}
|
||||
|
||||
impl_u64_flag!(ServerPermission, RoomPermission, RoomAttrs);
|
||||
impl_u64_flag!(ServerPermission, MemberPermission, RoomAttrs);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Reference in a new issue