Skip to content
Clone

feat(chat): expose the internal room authorization rpc

- add `ChatService::authorize_internal_room` reusing dir visibility and the
  space capability kernel for read/deliver/manage decisions
- register `internal.v1.ChatAuthorizationService` on the generated tonic trait
ZhenYi committed
parent 3951830 commit 16132e1 Browse files

5 files changed

+101 −0

File tree 5
Added lib/chat/src/rpc/mod.rs View lib/chat/src/rpc/mod.rs
@@ -0,0 +1,64 @@
1//! 内部 RPC 的 chat 域:只做协议转换,权限判定在 `ChatService`。
2
3use internal::Gate;
4use internal::server::InternalRouter;
5use proto::internal::v1::chat_authorization_service_server::{
6 ChatAuthorizationService as ChatAuthorizationTrait, ChatAuthorizationServiceServer,
7};
8use proto::internal::v1::{AuthorizationDecision, AuthorizeRoomRequest};
9use tonic::{Request, Response, Status};
10
11use crate::errors::ChatError;
12use crate::services::service::ChatService;
13
14pub fn register(router: InternalRouter, service: &ChatService, gate: Gate) -> InternalRouter {
15 router.add_service(ChatAuthorizationServiceServer::with_interceptor(
16 ChatRpc {
17 service: service.clone(),
18 },
19 gate,
20 ))
21}
22
23#[derive(Clone)]
24struct ChatRpc {
25 service: ChatService,
26}
27
28#[tonic::async_trait]
29impl ChatAuthorizationTrait for ChatRpc {
30 async fn authorize_room(
31 &self,
32 request: Request<AuthorizeRoomRequest>,
33 ) -> Result<Response<AuthorizationDecision>, Status> {
34 let _meta = internal::interceptor::meta(&request)?;
35 let request = request.into_inner();
36 let room_id = request
37 .room_id
38 .parse()
39 .map_err(|_| Status::invalid_argument("room_id must be a UUID"))?;
40 let allowed = self
41 .service
42 .authorize_internal_room(request.user_id, room_id, &request.action)
43 .await
44 .map_err(status_of)?;
45 Ok(Response::new(AuthorizationDecision {
46 allowed,
47 reason: String::new(),
48 resource_id: request.room_id,
49 }))
50 }
51}
52
53fn status_of(error: ChatError) -> Status {
54 match error {
55 ChatError::Unauthorized => Status::unauthenticated("chat authorization rejected"),
56 ChatError::Forbidden | ChatError::NotFound => Status::permission_denied("not allowed"),
57 ChatError::BadRequest(message) => Status::invalid_argument(message),
58 ChatError::RateLimited(message) => Status::resource_exhausted(message),
59 other => {
60 tracing::warn!(%other, "internal chat authorization failed");
61 Status::internal("chat authorization failed")
62 }
63 }
64}
Added lib/chat/src/services/internal_authz.rs View lib/chat/src/services/internal_authz.rs
@@ -0,0 +1,32 @@
1use uuid::Uuid;
2
3use crate::errors::ChatError;
4use crate::services::service::ChatService;
5
6impl ChatService {
7 /// 房间订阅/投递鉴权:读权限走 `is_room_member`(dir 覆盖项 + DM 回退),
8 /// 管理动作走 space 能力内核,规则仍只有一份。
9 pub async fn authorize_internal_room(
10 &self,
11 user_id: i64,
12 room_id: Uuid,
13 action: &str,
14 ) -> Result<bool, ChatError> {
15 match action {
16 "MANAGE" => {
17 let context = crate::share::lookup::lookup_room_context(self, room_id).await?;
18 match context.space.as_ref() {
19 Some(space) => {
20 crate::share::authz::can_manage_space(self, space, user_id).await
21 }
22 None => Ok(false),
23 }
24 }
25 _ => match crate::share::lookup::is_room_member(self, room_id, user_id).await {
26 Ok(visible) => Ok(visible),
27 Err(ChatError::NotFound) => Ok(false),
28 Err(error) => Err(error),
29 },
30 }
31 }
32}
Modified lib/chat/src/services/mod.rs View lib/chat/src/services/mod.rs
@@ -4,6 +4,7 @@
44pub mod dms;
55pub mod drafts;
66pub mod emojis;
7pub mod internal_authz;
78pub mod media;
89pub mod messages;
910pub mod outbound;
Modified lib/chat/src/lib.rs View lib/chat/src/lib.rs
@@ -2,6 +2,7 @@
22pub mod handlers;
33pub mod openapi;
44pub mod routes;
5pub mod rpc;
56pub mod services;
67pub mod share;
78pub mod worker;
Modified lib/chat/Cargo.toml View lib/chat/Cargo.toml
@@ -26,6 +26,9 @@
2626reqwest = { workspace = true }
2727livekit-api = { workspace = true }
2828livekit-protocol = { workspace = true }
29internal = { workspace = true }
30proto = { workspace = true }
31tonic = { workspace = true }
2932serde = { workspace = true, features = ["derive"] }
3033serde_json = { workspace = true }
3134encoding_rs = { workspace = true }