refactor: reorganize modules and update service references for improved clarity
ZhenYi committed
Modified app/src/main.rs app/src/main.rs +1 −3 View app/src/main.rs
@@ -81,9 +81,7 @@
8181 .await
8282 .context("building AppState")?;
8383 let auth_service = auth::AuthService::new(state.clone());
84 state.kenauth = Some(std::sync::Arc::new(auth::AuthApiAdapter::new(
85 auth_service,
86 )));
84 state.kenauth = Some(std::sync::Arc::new(auth_service));
8785 if let Some(client) = state.kenauth.clone() {
8886 export::identity_client::install(client);
8987 }
Modified lib/auth/src/handlers/account/devices.rs lib/auth/src/handlers/account/devices.rs +1 −2 View lib/auth/src/handlers/account/devices.rs
@@ -120,8 +120,7 @@
120120 Query(query): Query<PageQuery>,
121121) -> Result<AppResponseData<Vec<DeviceDto>>, ApiError> {
122122 let uid = session::require_uid(&session)?;
123 let client = rpc::client(&service.state)?;
124 let listed = client
123 let listed = service
125124 .list_user_sessions(
126125 uid,
127126 false,
Modified lib/auth/src/handlers/account/password.rs lib/auth/src/handlers/account/password.rs +1 −2 View lib/auth/src/handlers/account/password.rs
@@ -34,9 +34,8 @@
3434 Json(cmd): Json<ChangePasswordCmd>,
3535) -> Result<AppResponseData<NoData>, ApiError> {
3636 let uid = session::require_uid(&session)?;
37 let client = rpc::client(&service.state)?;
3837 let keep = session.session_id().unwrap_or_default();
39 client
38 service
4039 .change_password(
4140 uid,
4241 &rpc::digest(&cmd.old_password),
Modified lib/auth/src/handlers/account/sessions.rs lib/auth/src/handlers/account/sessions.rs +3 −6 View lib/auth/src/handlers/account/sessions.rs
@@ -127,8 +127,7 @@
127127 Query(query): Query<SessionQuery>,
128128) -> Result<AppResponseData<Page<SessionDto>>, ApiError> {
129129 let uid = session::require_uid(&session)?;
130 let client = rpc::client(&service.state)?;
131 let listed = client
130 let listed = service
132131 .list_user_sessions(
133132 uid,
134133 query.include_revoked.unwrap_or(false),
@@ -160,9 +159,8 @@
160159 session: Session,
161160) -> Result<AppResponseData<CountResp>, ApiError> {
162161 let uid = session::require_uid(&session)?;
163 let client = rpc::client(&service.state)?;
164162 let keep = session.session_id().unwrap_or_default();
165 let revoked = client
163 let revoked = service
166164 .revoke_user_sessions(uid, keep.as_str(), "user_logout", false)
167165 .await
168166 .map_err(|error| ApiError::internal(error.to_string()))?;
@@ -189,8 +187,7 @@
189187 Path(session_id): Path<Uuid>,
190188) -> Result<AppResponseData<NoData>, ApiError> {
191189 let uid = session::require_uid(&session)?;
192 let client = rpc::client(&service.state)?;
193 let revoked = client
190 let revoked = service
194191 .revoke_session(uid, &session_id.to_string(), "user_revoked")
195192 .await
196193 .map_err(|error| ApiError::internal(error.to_string()))?;
Modified lib/auth/src/handlers/account/two_factor.rs lib/auth/src/handlers/account/two_factor.rs +6 −12 View lib/auth/src/handlers/account/two_factor.rs
@@ -59,8 +59,7 @@
5959 session: Session,
6060) -> Result<AppResponseData<TotpStatusResp>, ApiError> {
6161 let uid = session::require_uid(&session)?;
62 let client = rpc::client(&service.state)?;
63 let status = client
62 let status = service
6463 .get_mfa_status(uid)
6564 .await
6665 .map_err(|error| ApiError::internal(error.to_string()))?;
@@ -88,8 +87,7 @@
8887 headers: HeaderMap,
8988) -> Result<AppResponseData<TotpSetupResp>, ApiError> {
9089 let uid = session::require_uid(&session)?;
91 let client = rpc::client(&service.state)?;
92 let configured = client
90 let configured = service
9391 .configure_totp(uid, "TOTP", &rpc::client_ip(&headers))
9492 .await
9593 .map_err(|error| ApiError::bad_request(error.to_string()))?;
@@ -118,8 +116,7 @@
118116 Json(cmd): Json<TotpEnableCmd>,
119117) -> Result<AppResponseData<RecoveryCodesResp>, ApiError> {
120118 let uid = session::require_uid(&session)?;
121 let client = rpc::client(&service.state)?;
122 let enabled = client
119 let enabled = service
123120 .enable_totp(
124121 uid,
125122 cmd.code.trim(),
@@ -155,8 +152,7 @@
155152 Json(cmd): Json<TotpCodeCmd>,
156153) -> Result<AppResponseData<NoData>, ApiError> {
157154 let uid = session::require_uid(&session)?;
158 let client = rpc::client(&service.state)?;
159 client
155 service
160156 .disable_totp(uid, cmd.code.trim(), &rpc::client_ip(&headers))
161157 .await
162158 .map_err(|error| ApiError::bad_request(error.to_string()))?;
@@ -178,8 +174,7 @@
178174 session: Session,
179175) -> Result<AppResponseData<RecoveryCodesResp>, ApiError> {
180176 let uid = session::require_uid(&session)?;
181 let client = rpc::client(&service.state)?;
182 let status = client
177 let status = service
183178 .get_mfa_status(uid)
184179 .await
185180 .map_err(|error| ApiError::internal(error.to_string()))?;
@@ -209,8 +204,7 @@
209204 Json(cmd): Json<RegenerateRecoveryCodesCmd>,
210205) -> Result<AppResponseData<RecoveryCodesResp>, ApiError> {
211206 let uid = session::require_uid(&session)?;
212 let client = rpc::client(&service.state)?;
213 let regenerated = client
207 let regenerated = service
214208 .regenerate_recovery_codes(uid, &rpc::digest(&cmd.password), &rpc::client_ip(&headers))
215209 .await
216210 .map_err(|error| ApiError::bad_request(error.to_string()))?;
Modified lib/auth/src/internal/bridge/agreement.rs lib/auth/src/internal/bridge/agreement.rs +2 −9 View lib/auth/src/internal/bridge/agreement.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::agreement::AgreementApi;
76use crate::protos::auth::v1::agreement_service_server::AgreementService;
@@ -20,20 +19,14 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::ListAgreementsRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::ListAgreementsResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.list_agreements(request).await
2824 }
2925 pub async fn check_agreements_accepted(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::CheckAgreementsAcceptedRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::CheckAgreementsAcceptedResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.check_agreements_accepted(request).await
3831 }
3932}
Modified lib/auth/src/internal/bridge/authority.rs lib/auth/src/internal/bridge/authority.rs +10 −41 View lib/auth/src/internal/bridge/authority.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::authority::AuthorityApi;
76use crate::protos::auth::internal::v1::authority_service_server::AuthorityService;
@@ -21,70 +20,49 @@
2120 mut request: Request<crate::protos::auth::internal::v1::RedeemSocketTicketRequest>,
2221 ) -> Result<Response<crate::protos::auth::internal::v1::RedeemSocketTicketResponse>, Status>
2322 {
24 request.extensions_mut().insert(RequestMeta {
25 request_id: Uuid::now_v7().to_string(),
26 caller_service: "kenapi".to_owned(),
27 });
23 request.extensions_mut().insert(RequestMeta::internal());
2824 self.api.redeem_socket_ticket(request).await
2925 }
3026 pub async fn validate(
3127 &mut self,
3228 mut request: Request<crate::protos::auth::internal::v1::ValidateRequest>,
3329 ) -> Result<Response<crate::protos::auth::internal::v1::ValidateResponse>, Status> {
34 request.extensions_mut().insert(RequestMeta {
35 request_id: Uuid::now_v7().to_string(),
36 caller_service: "kenapi".to_owned(),
37 });
30 request.extensions_mut().insert(RequestMeta::internal());
3831 self.api.validate(request).await
3932 }
4033 pub async fn get_user(
4134 &mut self,
4235 mut request: Request<crate::protos::auth::internal::v1::GetUserRequest>,
4336 ) -> Result<Response<crate::protos::auth::internal::v1::GetUserResponse>, Status> {
44 request.extensions_mut().insert(RequestMeta {
45 request_id: Uuid::now_v7().to_string(),
46 caller_service: "kenapi".to_owned(),
47 });
37 request.extensions_mut().insert(RequestMeta::internal());
4838 self.api.get_user(request).await
4939 }
5040 pub async fn batch_get_user(
5141 &mut self,
5242 mut request: Request<crate::protos::auth::internal::v1::BatchGetUserRequest>,
5343 ) -> Result<Response<crate::protos::auth::internal::v1::BatchGetUserResponse>, Status> {
54 request.extensions_mut().insert(RequestMeta {
55 request_id: Uuid::now_v7().to_string(),
56 caller_service: "kenapi".to_owned(),
57 });
44 request.extensions_mut().insert(RequestMeta::internal());
5845 self.api.batch_get_user(request).await
5946 }
6047 pub async fn get_session(
6148 &mut self,
6249 mut request: Request<crate::protos::auth::internal::v1::GetSessionRequest>,
6350 ) -> Result<Response<crate::protos::auth::internal::v1::GetSessionResponse>, Status> {
64 request.extensions_mut().insert(RequestMeta {
65 request_id: Uuid::now_v7().to_string(),
66 caller_service: "kenapi".to_owned(),
67 });
51 request.extensions_mut().insert(RequestMeta::internal());
6852 self.api.get_session(request).await
6953 }
7054 pub async fn update_user(
7155 &mut self,
7256 mut request: Request<crate::protos::auth::internal::v1::UpdateUserRequest>,
7357 ) -> Result<Response<crate::protos::auth::internal::v1::UpdateUserResponse>, Status> {
74 request.extensions_mut().insert(RequestMeta {
75 request_id: Uuid::now_v7().to_string(),
76 caller_service: "kenapi".to_owned(),
77 });
58 request.extensions_mut().insert(RequestMeta::internal());
7859 self.api.update_user(request).await
7960 }
8061 pub async fn delete_account(
8162 &mut self,
8263 mut request: Request<crate::protos::auth::internal::v1::DeleteAccountRequest>,
8364 ) -> Result<Response<crate::protos::auth::internal::v1::DeleteAccountResponse>, Status> {
84 request.extensions_mut().insert(RequestMeta {
85 request_id: Uuid::now_v7().to_string(),
86 caller_service: "kenapi".to_owned(),
87 });
65 request.extensions_mut().insert(RequestMeta::internal());
8866 self.api.delete_account(request).await
8967 }
9068 pub async fn cancel_account_deletion(
@@ -92,30 +70,21 @@
9270 mut request: Request<crate::protos::auth::internal::v1::CancelAccountDeletionRequest>,
9371 ) -> Result<Response<crate::protos::auth::internal::v1::CancelAccountDeletionResponse>, Status>
9472 {
95 request.extensions_mut().insert(RequestMeta {
96 request_id: Uuid::now_v7().to_string(),
97 caller_service: "kenapi".to_owned(),
98 });
73 request.extensions_mut().insert(RequestMeta::internal());
9974 self.api.cancel_account_deletion(request).await
10075 }
10176 pub async fn purge_account(
10277 &mut self,
10378 mut request: Request<crate::protos::auth::internal::v1::PurgeAccountRequest>,
10479 ) -> Result<Response<crate::protos::auth::internal::v1::PurgeAccountResponse>, Status> {
105 request.extensions_mut().insert(RequestMeta {
106 request_id: Uuid::now_v7().to_string(),
107 caller_service: "kenapi".to_owned(),
108 });
80 request.extensions_mut().insert(RequestMeta::internal());
10981 self.api.purge_account(request).await
11082 }
11183 pub async fn list_users(
11284 &mut self,
11385 mut request: Request<crate::protos::auth::internal::v1::ListUsersRequest>,
11486 ) -> Result<Response<crate::protos::auth::internal::v1::ListUsersResponse>, Status> {
115 request.extensions_mut().insert(RequestMeta {
116 request_id: Uuid::now_v7().to_string(),
117 caller_service: "kenapi".to_owned(),
118 });
87 request.extensions_mut().insert(RequestMeta::internal());
11988 self.api.list_users(request).await
12089 }
12190}
Modified lib/auth/src/internal/bridge/authorization.rs lib/auth/src/internal/bridge/authorization.rs +4 −17 View lib/auth/src/internal/bridge/authorization.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::authorization::AuthorizationApi;
76use crate::protos::auth::v1::authorization_service_server::AuthorizationService;
@@ -20,40 +19,28 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::AuthorizeRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::AuthorizeResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.authorize(request).await
2824 }
2925 pub async fn batch_authorize(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::BatchAuthorizeRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::BatchAuthorizeResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.batch_authorize(request).await
3831 }
3932 pub async fn is_platform_admin(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::IsPlatformAdminRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::IsPlatformAdminResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.is_platform_admin(request).await
4838 }
4939 pub async fn get_capabilities(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::GetCapabilitiesRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::GetCapabilitiesResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.get_capabilities(request).await
5845 }
5946}
Modified lib/auth/src/internal/bridge/identity.rs lib/auth/src/internal/bridge/identity.rs +7 −29 View lib/auth/src/internal/bridge/identity.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::identity::IdentityApi;
76use crate::protos::auth::v1::identity_service_server::IdentityService;
@@ -20,70 +19,49 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::AuthenticateRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::AuthenticateResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.authenticate(request).await
2824 }
2925 pub async fn verify_credential(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::VerifyCredentialRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::VerifyCredentialResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.verify_credential(request).await
3831 }
3932 pub async fn get_user_public(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::GetUserPublicRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::GetUserPublicResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.get_user_public(request).await
4838 }
4939 pub async fn batch_get_user_public(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::BatchGetUserPublicRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::BatchGetUserPublicResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.batch_get_user_public(request).await
5845 }
5946 pub async fn get_user_status(
6047 &mut self,
6148 mut request: Request<crate::protos::auth::v1::GetUserStatusRequest>,
6249 ) -> Result<Response<crate::protos::auth::v1::GetUserStatusResponse>, Status> {
63 request.extensions_mut().insert(RequestMeta {
64 request_id: Uuid::now_v7().to_string(),
65 caller_service: "kenapi".to_owned(),
66 });
50 request.extensions_mut().insert(RequestMeta::internal());
6751 self.api.get_user_status(request).await
6852 }
6953 pub async fn ensure_bot_user(
7054 &mut self,
7155 mut request: Request<crate::protos::auth::v1::EnsureBotUserRequest>,
7256 ) -> Result<Response<crate::protos::auth::v1::EnsureBotUserResponse>, Status> {
73 request.extensions_mut().insert(RequestMeta {
74 request_id: Uuid::now_v7().to_string(),
75 caller_service: "kenapi".to_owned(),
76 });
57 request.extensions_mut().insert(RequestMeta::internal());
7758 self.api.ensure_bot_user(request).await
7859 }
7960 pub async fn change_password(
8061 &mut self,
8162 mut request: Request<crate::protos::auth::v1::ChangePasswordRequest>,
8263 ) -> Result<Response<crate::protos::auth::v1::ChangePasswordResponse>, Status> {
83 request.extensions_mut().insert(RequestMeta {
84 request_id: Uuid::now_v7().to_string(),
85 caller_service: "kenapi".to_owned(),
86 });
64 request.extensions_mut().insert(RequestMeta::internal());
8765 self.api.change_password(request).await
8866 }
8967}
Modified lib/auth/src/internal/bridge/mfa.rs lib/auth/src/internal/bridge/mfa.rs +7 −29 View lib/auth/src/internal/bridge/mfa.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::mfa::MfaApi;
76use crate::protos::auth::v1::mfa_service_server::MfaService;
@@ -20,70 +19,49 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::GetMfaStatusRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::GetMfaStatusResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.get_mfa_status(request).await
2824 }
2925 pub async fn verify_totp(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::VerifyTotpRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::VerifyTotpResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.verify_totp(request).await
3831 }
3932 pub async fn redeem_recovery_code(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::RedeemRecoveryCodeRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::RedeemRecoveryCodeResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.redeem_recovery_code(request).await
4838 }
4939 pub async fn configure_totp(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::ConfigureTotpRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::ConfigureTotpResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.configure_totp(request).await
5845 }
5946 pub async fn enable_totp(
6047 &mut self,
6148 mut request: Request<crate::protos::auth::v1::EnableTotpRequest>,
6249 ) -> Result<Response<crate::protos::auth::v1::EnableTotpResponse>, Status> {
63 request.extensions_mut().insert(RequestMeta {
64 request_id: Uuid::now_v7().to_string(),
65 caller_service: "kenapi".to_owned(),
66 });
50 request.extensions_mut().insert(RequestMeta::internal());
6751 self.api.enable_totp(request).await
6852 }
6953 pub async fn disable_totp(
7054 &mut self,
7155 mut request: Request<crate::protos::auth::v1::DisableTotpRequest>,
7256 ) -> Result<Response<crate::protos::auth::v1::DisableTotpResponse>, Status> {
73 request.extensions_mut().insert(RequestMeta {
74 request_id: Uuid::now_v7().to_string(),
75 caller_service: "kenapi".to_owned(),
76 });
57 request.extensions_mut().insert(RequestMeta::internal());
7758 self.api.disable_totp(request).await
7859 }
7960 pub async fn regenerate_recovery_codes(
8061 &mut self,
8162 mut request: Request<crate::protos::auth::v1::RegenerateRecoveryCodesRequest>,
8263 ) -> Result<Response<crate::protos::auth::v1::RegenerateRecoveryCodesResponse>, Status> {
83 request.extensions_mut().insert(RequestMeta {
84 request_id: Uuid::now_v7().to_string(),
85 caller_service: "kenapi".to_owned(),
86 });
64 request.extensions_mut().insert(RequestMeta::internal());
8765 self.api.regenerate_recovery_codes(request).await
8866 }
8967}
Modified lib/auth/src/internal/bridge/organization.rs lib/auth/src/internal/bridge/organization.rs +49 −197 View lib/auth/src/internal/bridge/organization.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::organization::OrganizationApi;
76use crate::protos::auth::v1::organization_service_server::OrganizationService;
@@ -20,490 +19,343 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::CreateOrganizationRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::CreateOrganizationResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.create_organization(request).await
2824 }
2925 pub async fn get_organization(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::GetOrganizationRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::GetOrganizationResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.get_organization(request).await
3831 }
3932 pub async fn list_organizations(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::ListOrganizationsRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::ListOrganizationsResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.list_organizations(request).await
4838 }
4939 pub async fn list_public_organizations(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::ListPublicOrganizationsRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::ListPublicOrganizationsResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.list_public_organizations(request).await
5845 }
5946 pub async fn update_organization(
6047 &mut self,
6148 mut request: Request<crate::protos::auth::v1::UpdateOrganizationRequest>,
6249 ) -> Result<Response<crate::protos::auth::v1::UpdateOrganizationResponse>, Status> {
63 request.extensions_mut().insert(RequestMeta {
64 request_id: Uuid::now_v7().to_string(),
65 caller_service: "kenapi".to_owned(),
66 });
50 request.extensions_mut().insert(RequestMeta::internal());
6751 self.api.update_organization(request).await
6852 }
6953 pub async fn archive_organization(
7054 &mut self,
7155 mut request: Request<crate::protos::auth::v1::ArchiveOrganizationRequest>,
7256 ) -> Result<Response<crate::protos::auth::v1::ArchiveOrganizationResponse>, Status> {
73 request.extensions_mut().insert(RequestMeta {
74 request_id: Uuid::now_v7().to_string(),
75 caller_service: "kenapi".to_owned(),
76 });
57 request.extensions_mut().insert(RequestMeta::internal());
7758 self.api.archive_organization(request).await
7859 }
7960 pub async fn restore_organization(
8061 &mut self,
8162 mut request: Request<crate::protos::auth::v1::RestoreOrganizationRequest>,
8263 ) -> Result<Response<crate::protos::auth::v1::RestoreOrganizationResponse>, Status> {
83 request.extensions_mut().insert(RequestMeta {
84 request_id: Uuid::now_v7().to_string(),
85 caller_service: "kenapi".to_owned(),
86 });
64 request.extensions_mut().insert(RequestMeta::internal());
8765 self.api.restore_organization(request).await
8866 }
8967 pub async fn delete_organization(
9068 &mut self,
9169 mut request: Request<crate::protos::auth::v1::DeleteOrganizationRequest>,
9270 ) -> Result<Response<crate::protos::auth::v1::DeleteOrganizationResponse>, Status> {
93 request.extensions_mut().insert(RequestMeta {
94 request_id: Uuid::now_v7().to_string(),
95 caller_service: "kenapi".to_owned(),
96 });
71 request.extensions_mut().insert(RequestMeta::internal());
9772 self.api.delete_organization(request).await
9873 }
9974 pub async fn transfer_ownership(
10075 &mut self,
10176 mut request: Request<crate::protos::auth::v1::TransferOwnershipRequest>,
10277 ) -> Result<Response<crate::protos::auth::v1::TransferOwnershipResponse>, Status> {
103 request.extensions_mut().insert(RequestMeta {
104 request_id: Uuid::now_v7().to_string(),
105 caller_service: "kenapi".to_owned(),
106 });
78 request.extensions_mut().insert(RequestMeta::internal());
10779 self.api.transfer_ownership(request).await
10880 }
10981 pub async fn add_member(
11082 &mut self,
11183 mut request: Request<crate::protos::auth::v1::AddMemberRequest>,
11284 ) -> Result<Response<crate::protos::auth::v1::AddMemberResponse>, Status> {
113 request.extensions_mut().insert(RequestMeta {
114 request_id: Uuid::now_v7().to_string(),
115 caller_service: "kenapi".to_owned(),
116 });
85 request.extensions_mut().insert(RequestMeta::internal());
11786 self.api.add_member(request).await
11887 }
11988 pub async fn remove_member(
12089 &mut self,
12190 mut request: Request<crate::protos::auth::v1::RemoveMemberRequest>,
12291 ) -> Result<Response<crate::protos::auth::v1::RemoveMemberResponse>, Status> {
123 request.extensions_mut().insert(RequestMeta {
124 request_id: Uuid::now_v7().to_string(),
125 caller_service: "kenapi".to_owned(),
126 });
92 request.extensions_mut().insert(RequestMeta::internal());
12793 self.api.remove_member(request).await
12894 }
12995 pub async fn leave_organization(
13096 &mut self,
13197 mut request: Request<crate::protos::auth::v1::LeaveOrganizationRequest>,
13298 ) -> Result<Response<crate::protos::auth::v1::LeaveOrganizationResponse>, Status> {
133 request.extensions_mut().insert(RequestMeta {
134 request_id: Uuid::now_v7().to_string(),
135 caller_service: "kenapi".to_owned(),
136 });
99 request.extensions_mut().insert(RequestMeta::internal());
137100 self.api.leave_organization(request).await
138101 }
139102 pub async fn list_members(
140103 &mut self,
141104 mut request: Request<crate::protos::auth::v1::ListMembersRequest>,
142105 ) -> Result<Response<crate::protos::auth::v1::ListMembersResponse>, Status> {
143 request.extensions_mut().insert(RequestMeta {
144 request_id: Uuid::now_v7().to_string(),
145 caller_service: "kenapi".to_owned(),
146 });
106 request.extensions_mut().insert(RequestMeta::internal());
147107 self.api.list_members(request).await
148108 }
149109 pub async fn set_member_role(
150110 &mut self,
151111 mut request: Request<crate::protos::auth::v1::SetMemberRoleRequest>,
152112 ) -> Result<Response<crate::protos::auth::v1::SetMemberRoleResponse>, Status> {
153 request.extensions_mut().insert(RequestMeta {
154 request_id: Uuid::now_v7().to_string(),
155 caller_service: "kenapi".to_owned(),
156 });
113 request.extensions_mut().insert(RequestMeta::internal());
157114 self.api.set_member_role(request).await
158115 }
159116 pub async fn ban_member(
160117 &mut self,
161118 mut request: Request<crate::protos::auth::v1::BanMemberRequest>,
162119 ) -> Result<Response<crate::protos::auth::v1::BanMemberResponse>, Status> {
163 request.extensions_mut().insert(RequestMeta {
164 request_id: Uuid::now_v7().to_string(),
165 caller_service: "kenapi".to_owned(),
166 });
120 request.extensions_mut().insert(RequestMeta::internal());
167121 self.api.ban_member(request).await
168122 }
169123 pub async fn unban_member(
170124 &mut self,
171125 mut request: Request<crate::protos::auth::v1::UnbanMemberRequest>,
172126 ) -> Result<Response<crate::protos::auth::v1::UnbanMemberResponse>, Status> {
173 request.extensions_mut().insert(RequestMeta {
174 request_id: Uuid::now_v7().to_string(),
175 caller_service: "kenapi".to_owned(),
176 });
127 request.extensions_mut().insert(RequestMeta::internal());
177128 self.api.unban_member(request).await
178129 }
179130 pub async fn get_member(
180131 &mut self,
181132 mut request: Request<crate::protos::auth::v1::GetMemberRequest>,
182133 ) -> Result<Response<crate::protos::auth::v1::GetMemberResponse>, Status> {
183 request.extensions_mut().insert(RequestMeta {
184 request_id: Uuid::now_v7().to_string(),
185 caller_service: "kenapi".to_owned(),
186 });
134 request.extensions_mut().insert(RequestMeta::internal());
187135 self.api.get_member(request).await
188136 }
189137 pub async fn update_member(
190138 &mut self,
191139 mut request: Request<crate::protos::auth::v1::UpdateMemberRequest>,
192140 ) -> Result<Response<crate::protos::auth::v1::UpdateMemberResponse>, Status> {
193 request.extensions_mut().insert(RequestMeta {
194 request_id: Uuid::now_v7().to_string(),
195 caller_service: "kenapi".to_owned(),
196 });
141 request.extensions_mut().insert(RequestMeta::internal());
197142 self.api.update_member(request).await
198143 }
199144 pub async fn update_self_member(
200145 &mut self,
201146 mut request: Request<crate::protos::auth::v1::UpdateMemberRequest>,
202147 ) -> Result<Response<crate::protos::auth::v1::UpdateMemberResponse>, Status> {
203 request.extensions_mut().insert(RequestMeta {
204 request_id: Uuid::now_v7().to_string(),
205 caller_service: "kenapi".to_owned(),
206 });
148 request.extensions_mut().insert(RequestMeta::internal());
207149 self.api.update_self_member(request).await
208150 }
209151 pub async fn list_bans(
210152 &mut self,
211153 mut request: Request<crate::protos::auth::v1::ListBansRequest>,
212154 ) -> Result<Response<crate::protos::auth::v1::ListBansResponse>, Status> {
213 request.extensions_mut().insert(RequestMeta {
214 request_id: Uuid::now_v7().to_string(),
215 caller_service: "kenapi".to_owned(),
216 });
155 request.extensions_mut().insert(RequestMeta::internal());
217156 self.api.list_bans(request).await
218157 }
219158 pub async fn get_ban(
220159 &mut self,
221160 mut request: Request<crate::protos::auth::v1::GetBanRequest>,
222161 ) -> Result<Response<crate::protos::auth::v1::GetBanResponse>, Status> {
223 request.extensions_mut().insert(RequestMeta {
224 request_id: Uuid::now_v7().to_string(),
225 caller_service: "kenapi".to_owned(),
226 });
162 request.extensions_mut().insert(RequestMeta::internal());
227163 self.api.get_ban(request).await
228164 }
229165 pub async fn update_ban(
230166 &mut self,
231167 mut request: Request<crate::protos::auth::v1::UpdateBanRequest>,
232168 ) -> Result<Response<crate::protos::auth::v1::UpdateBanResponse>, Status> {
233 request.extensions_mut().insert(RequestMeta {
234 request_id: Uuid::now_v7().to_string(),
235 caller_service: "kenapi".to_owned(),
236 });
169 request.extensions_mut().insert(RequestMeta::internal());
237170 self.api.update_ban(request).await
238171 }
239172 pub async fn get_role(
240173 &mut self,
241174 mut request: Request<crate::protos::auth::v1::GetRoleRequest>,
242175 ) -> Result<Response<crate::protos::auth::v1::GetRoleResponse>, Status> {
243 request.extensions_mut().insert(RequestMeta {
244 request_id: Uuid::now_v7().to_string(),
245 caller_service: "kenapi".to_owned(),
246 });
176 request.extensions_mut().insert(RequestMeta::internal());
247177 self.api.get_role(request).await
248178 }
249179 pub async fn join_role(
250180 &mut self,
251181 mut request: Request<crate::protos::auth::v1::JoinRoleRequest>,
252182 ) -> Result<Response<crate::protos::auth::v1::JoinRoleResponse>, Status> {
253 request.extensions_mut().insert(RequestMeta {
254 request_id: Uuid::now_v7().to_string(),
255 caller_service: "kenapi".to_owned(),
256 });
183 request.extensions_mut().insert(RequestMeta::internal());
257184 self.api.join_role(request).await
258185 }
259186 pub async fn leave_role(
260187 &mut self,
261188 mut request: Request<crate::protos::auth::v1::LeaveRoleRequest>,
262189 ) -> Result<Response<crate::protos::auth::v1::LeaveRoleResponse>, Status> {
263 request.extensions_mut().insert(RequestMeta {
264 request_id: Uuid::now_v7().to_string(),
265 caller_service: "kenapi".to_owned(),
266 });
190 request.extensions_mut().insert(RequestMeta::internal());
267191 self.api.leave_role(request).await
268192 }
269193 pub async fn create_role(
270194 &mut self,
271195 mut request: Request<crate::protos::auth::v1::CreateRoleRequest>,
272196 ) -> Result<Response<crate::protos::auth::v1::CreateRoleResponse>, Status> {
273 request.extensions_mut().insert(RequestMeta {
274 request_id: Uuid::now_v7().to_string(),
275 caller_service: "kenapi".to_owned(),
276 });
197 request.extensions_mut().insert(RequestMeta::internal());
277198 self.api.create_role(request).await
278199 }
279200 pub async fn update_role(
280201 &mut self,
281202 mut request: Request<crate::protos::auth::v1::UpdateRoleRequest>,
282203 ) -> Result<Response<crate::protos::auth::v1::UpdateRoleResponse>, Status> {
283 request.extensions_mut().insert(RequestMeta {
284 request_id: Uuid::now_v7().to_string(),
285 caller_service: "kenapi".to_owned(),
286 });
204 request.extensions_mut().insert(RequestMeta::internal());
287205 self.api.update_role(request).await
288206 }
289207 pub async fn delete_role(
290208 &mut self,
291209 mut request: Request<crate::protos::auth::v1::DeleteRoleRequest>,
292210 ) -> Result<Response<crate::protos::auth::v1::DeleteRoleResponse>, Status> {
293 request.extensions_mut().insert(RequestMeta {
294 request_id: Uuid::now_v7().to_string(),
295 caller_service: "kenapi".to_owned(),
296 });
211 request.extensions_mut().insert(RequestMeta::internal());
297212 self.api.delete_role(request).await
298213 }
299214 pub async fn list_roles(
300215 &mut self,
301216 mut request: Request<crate::protos::auth::v1::ListRolesRequest>,
302217 ) -> Result<Response<crate::protos::auth::v1::ListRolesResponse>, Status> {
303 request.extensions_mut().insert(RequestMeta {
304 request_id: Uuid::now_v7().to_string(),
305 caller_service: "kenapi".to_owned(),
306 });
218 request.extensions_mut().insert(RequestMeta::internal());
307219 self.api.list_roles(request).await
308220 }
309221 pub async fn set_role_permissions(
310222 &mut self,
311223 mut request: Request<crate::protos::auth::v1::SetRolePermissionsRequest>,
312224 ) -> Result<Response<crate::protos::auth::v1::SetRolePermissionsResponse>, Status> {
313 request.extensions_mut().insert(RequestMeta {
314 request_id: Uuid::now_v7().to_string(),
315 caller_service: "kenapi".to_owned(),
316 });
225 request.extensions_mut().insert(RequestMeta::internal());
317226 self.api.set_role_permissions(request).await
318227 }
319228 pub async fn assign_role(
320229 &mut self,
321230 mut request: Request<crate::protos::auth::v1::AssignRoleRequest>,
322231 ) -> Result<Response<crate::protos::auth::v1::AssignRoleResponse>, Status> {
323 request.extensions_mut().insert(RequestMeta {
324 request_id: Uuid::now_v7().to_string(),
325 caller_service: "kenapi".to_owned(),
326 });
232 request.extensions_mut().insert(RequestMeta::internal());
327233 self.api.assign_role(request).await
328234 }
329235 pub async fn unassign_role(
330236 &mut self,
331237 mut request: Request<crate::protos::auth::v1::UnassignRoleRequest>,
332238 ) -> Result<Response<crate::protos::auth::v1::UnassignRoleResponse>, Status> {
333 request.extensions_mut().insert(RequestMeta {
334 request_id: Uuid::now_v7().to_string(),
335 caller_service: "kenapi".to_owned(),
336 });
239 request.extensions_mut().insert(RequestMeta::internal());
337240 self.api.unassign_role(request).await
338241 }
339242 pub async fn set_member_permissions(
340243 &mut self,
341244 mut request: Request<crate::protos::auth::v1::SetMemberPermissionsRequest>,
342245 ) -> Result<Response<crate::protos::auth::v1::SetMemberPermissionsResponse>, Status> {
343 request.extensions_mut().insert(RequestMeta {
344 request_id: Uuid::now_v7().to_string(),
345 caller_service: "kenapi".to_owned(),
346 });
246 request.extensions_mut().insert(RequestMeta::internal());
347247 self.api.set_member_permissions(request).await
348248 }
349249 pub async fn check_permission(
350250 &mut self,
351251 mut request: Request<crate::protos::auth::v1::CheckPermissionRequest>,
352252 ) -> Result<Response<crate::protos::auth::v1::CheckPermissionResponse>, Status> {
353 request.extensions_mut().insert(RequestMeta {
354 request_id: Uuid::now_v7().to_string(),
355 caller_service: "kenapi".to_owned(),
356 });
253 request.extensions_mut().insert(RequestMeta::internal());
357254 self.api.check_permission(request).await
358255 }
359256 pub async fn check_permissions(
360257 &mut self,
361258 mut request: Request<crate::protos::auth::v1::CheckPermissionsRequest>,
362259 ) -> Result<Response<crate::protos::auth::v1::CheckPermissionsResponse>, Status> {
363 request.extensions_mut().insert(RequestMeta {
364 request_id: Uuid::now_v7().to_string(),
365 caller_service: "kenapi".to_owned(),
366 });
260 request.extensions_mut().insert(RequestMeta::internal());
367261 self.api.check_permissions(request).await
368262 }
369263 pub async fn create_invite(
370264 &mut self,
371265 mut request: Request<crate::protos::auth::v1::CreateInviteRequest>,
372266 ) -> Result<Response<crate::protos::auth::v1::CreateInviteResponse>, Status> {
373 request.extensions_mut().insert(RequestMeta {
374 request_id: Uuid::now_v7().to_string(),
375 caller_service: "kenapi".to_owned(),
376 });
267 request.extensions_mut().insert(RequestMeta::internal());
377268 self.api.create_invite(request).await
378269 }
379270 pub async fn revoke_invite(
380271 &mut self,
381272 mut request: Request<crate::protos::auth::v1::RevokeInviteRequest>,
382273 ) -> Result<Response<crate::protos::auth::v1::RevokeInviteResponse>, Status> {
383 request.extensions_mut().insert(RequestMeta {
384 request_id: Uuid::now_v7().to_string(),
385 caller_service: "kenapi".to_owned(),
386 });
274 request.extensions_mut().insert(RequestMeta::internal());
387275 self.api.revoke_invite(request).await
388276 }
389277 pub async fn list_invites(
390278 &mut self,
391279 mut request: Request<crate::protos::auth::v1::ListInvitesRequest>,
392280 ) -> Result<Response<crate::protos::auth::v1::ListInvitesResponse>, Status> {
393 request.extensions_mut().insert(RequestMeta {
394 request_id: Uuid::now_v7().to_string(),
395 caller_service: "kenapi".to_owned(),
396 });
281 request.extensions_mut().insert(RequestMeta::internal());
397282 self.api.list_invites(request).await
398283 }
399284 pub async fn redeem_invite(
400285 &mut self,
401286 mut request: Request<crate::protos::auth::v1::RedeemInviteRequest>,
402287 ) -> Result<Response<crate::protos::auth::v1::RedeemInviteResponse>, Status> {
403 request.extensions_mut().insert(RequestMeta {
404 request_id: Uuid::now_v7().to_string(),
405 caller_service: "kenapi".to_owned(),
406 });
288 request.extensions_mut().insert(RequestMeta::internal());
407289 self.api.redeem_invite(request).await
408290 }
409291 pub async fn create_join_rule(
410292 &mut self,
411293 mut request: Request<crate::protos::auth::v1::CreateJoinRuleRequest>,
412294 ) -> Result<Response<crate::protos::auth::v1::CreateJoinRuleResponse>, Status> {
413 request.extensions_mut().insert(RequestMeta {
414 request_id: Uuid::now_v7().to_string(),
415 caller_service: "kenapi".to_owned(),
416 });
295 request.extensions_mut().insert(RequestMeta::internal());
417296 self.api.create_join_rule(request).await
418297 }
419298 pub async fn update_join_rule(
420299 &mut self,
421300 mut request: Request<crate::protos::auth::v1::UpdateJoinRuleRequest>,
422301 ) -> Result<Response<crate::protos::auth::v1::UpdateJoinRuleResponse>, Status> {
423 request.extensions_mut().insert(RequestMeta {
424 request_id: Uuid::now_v7().to_string(),
425 caller_service: "kenapi".to_owned(),
426 });
302 request.extensions_mut().insert(RequestMeta::internal());
427303 self.api.update_join_rule(request).await
428304 }
429305 pub async fn delete_join_rule(
430306 &mut self,
431307 mut request: Request<crate::protos::auth::v1::DeleteJoinRuleRequest>,
432308 ) -> Result<Response<crate::protos::auth::v1::DeleteJoinRuleResponse>, Status> {
433 request.extensions_mut().insert(RequestMeta {
434 request_id: Uuid::now_v7().to_string(),
435 caller_service: "kenapi".to_owned(),
436 });
309 request.extensions_mut().insert(RequestMeta::internal());
437310 self.api.delete_join_rule(request).await
438311 }
439312 pub async fn list_join_rules(
440313 &mut self,
441314 mut request: Request<crate::protos::auth::v1::ListJoinRulesRequest>,
442315 ) -> Result<Response<crate::protos::auth::v1::ListJoinRulesResponse>, Status> {
443 request.extensions_mut().insert(RequestMeta {
444 request_id: Uuid::now_v7().to_string(),
445 caller_service: "kenapi".to_owned(),
446 });
316 request.extensions_mut().insert(RequestMeta::internal());
447317 self.api.list_join_rules(request).await
448318 }
449319 pub async fn create_join_request(
450320 &mut self,
451321 mut request: Request<crate::protos::auth::v1::CreateJoinRequestRequest>,
452322 ) -> Result<Response<crate::protos::auth::v1::CreateJoinRequestResponse>, Status> {
453 request.extensions_mut().insert(RequestMeta {
454 request_id: Uuid::now_v7().to_string(),
455 caller_service: "kenapi".to_owned(),
456 });
323 request.extensions_mut().insert(RequestMeta::internal());
457324 self.api.create_join_request(request).await
458325 }
459326 pub async fn list_join_requests(
460327 &mut self,
461328 mut request: Request<crate::protos::auth::v1::ListJoinRequestsRequest>,
462329 ) -> Result<Response<crate::protos::auth::v1::ListJoinRequestsResponse>, Status> {
463 request.extensions_mut().insert(RequestMeta {
464 request_id: Uuid::now_v7().to_string(),
465 caller_service: "kenapi".to_owned(),
466 });
330 request.extensions_mut().insert(RequestMeta::internal());
467331 self.api.list_join_requests(request).await
468332 }
469333 pub async fn withdraw_join_request(
470334 &mut self,
471335 mut request: Request<crate::protos::auth::v1::WithdrawJoinRequestRequest>,
472336 ) -> Result<Response<crate::protos::auth::v1::WithdrawJoinRequestResponse>, Status> {
473 request.extensions_mut().insert(RequestMeta {
474 request_id: Uuid::now_v7().to_string(),
475 caller_service: "kenapi".to_owned(),
476 });
337 request.extensions_mut().insert(RequestMeta::internal());
477338 self.api.withdraw_join_request(request).await
478339 }
479340 pub async fn approve_join_request(
480341 &mut self,
481342 mut request: Request<crate::protos::auth::v1::ApproveJoinRequestRequest>,
482343 ) -> Result<Response<crate::protos::auth::v1::ApproveJoinRequestResponse>, Status> {
483 request.extensions_mut().insert(RequestMeta {
484 request_id: Uuid::now_v7().to_string(),
485 caller_service: "kenapi".to_owned(),
486 });
344 request.extensions_mut().insert(RequestMeta::internal());
487345 self.api.approve_join_request(request).await
488346 }
489347 pub async fn reject_join_request(
490348 &mut self,
491349 mut request: Request<crate::protos::auth::v1::RejectJoinRequestRequest>,
492350 ) -> Result<Response<crate::protos::auth::v1::RejectJoinRequestResponse>, Status> {
493 request.extensions_mut().insert(RequestMeta {
494 request_id: Uuid::now_v7().to_string(),
495 caller_service: "kenapi".to_owned(),
496 });
351 request.extensions_mut().insert(RequestMeta::internal());
497352 self.api.reject_join_request(request).await
498353 }
499354 pub async fn ensure_bot_member(
500355 &mut self,
501356 mut request: Request<crate::protos::auth::v1::EnsureBotMemberRequest>,
502357 ) -> Result<Response<crate::protos::auth::v1::EnsureBotMemberResponse>, Status> {
503 request.extensions_mut().insert(RequestMeta {
504 request_id: Uuid::now_v7().to_string(),
505 caller_service: "kenapi".to_owned(),
506 });
358 request.extensions_mut().insert(RequestMeta::internal());
507359 self.api.ensure_bot_member(request).await
508360 }
509361}
Modified lib/auth/src/internal/bridge/session.rs lib/auth/src/internal/bridge/session.rs +4 −17 View lib/auth/src/internal/bridge/session.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::session::SessionApi;
76use crate::protos::auth::v1::session_service_server::SessionService;
@@ -20,40 +19,28 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::ListUserSessionsRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::ListUserSessionsResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.list_user_sessions(request).await
2824 }
2925 pub async fn revoke_session(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::RevokeSessionRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::RevokeSessionResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.revoke_session(request).await
3831 }
3932 pub async fn revoke_user_sessions(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::RevokeUserSessionsRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::RevokeUserSessionsResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.revoke_user_sessions(request).await
4838 }
4939 pub async fn bump_security_version(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::BumpSecurityVersionRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::BumpSecurityVersionResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.bump_security_version(request).await
5845 }
5946}
Modified lib/auth/src/internal/bridge/ssh_key.rs lib/auth/src/internal/bridge/ssh_key.rs +4 −17 View lib/auth/src/internal/bridge/ssh_key.rs
@@ -1,7 +1,6 @@
11use crate::AuthSvc;
22use crate::internal::context::RequestMeta;
33use tonic::{Request, Response, Status};
4use uuid::Uuid;
54
65use crate::internal::ssh_key::SshKeyApi;
76use crate::protos::auth::v1::ssh_key_service_server::SshKeyService;
@@ -20,40 +19,28 @@
2019 &mut self,
2120 mut request: Request<crate::protos::auth::v1::LookupSshKeyRequest>,
2221 ) -> Result<Response<crate::protos::auth::v1::LookupSshKeyResponse>, Status> {
23 request.extensions_mut().insert(RequestMeta {
24 request_id: Uuid::now_v7().to_string(),
25 caller_service: "kenapi".to_owned(),
26 });
22 request.extensions_mut().insert(RequestMeta::internal());
2723 self.api.lookup_ssh_key(request).await
2824 }
2925 pub async fn add_ssh_key(
3026 &mut self,
3127 mut request: Request<crate::protos::auth::v1::AddSshKeyRequest>,
3228 ) -> Result<Response<crate::protos::auth::v1::AddSshKeyResponse>, Status> {
33 request.extensions_mut().insert(RequestMeta {
34 request_id: Uuid::now_v7().to_string(),
35 caller_service: "kenapi".to_owned(),
36 });
29 request.extensions_mut().insert(RequestMeta::internal());
3730 self.api.add_ssh_key(request).await
3831 }
3932 pub async fn list_user_ssh_keys(
4033 &mut self,
4134 mut request: Request<crate::protos::auth::v1::ListUserSshKeysRequest>,
4235 ) -> Result<Response<crate::protos::auth::v1::ListUserSshKeysResponse>, Status> {
43 request.extensions_mut().insert(RequestMeta {
44 request_id: Uuid::now_v7().to_string(),
45 caller_service: "kenapi".to_owned(),
46 });
36 request.extensions_mut().insert(RequestMeta::internal());
4737 self.api.list_user_ssh_keys(request).await
4838 }
4939 pub async fn revoke_ssh_key(
5040 &mut self,
5141 mut request: Request<crate::protos::auth::v1::RevokeSshKeyRequest>,
5242 ) -> Result<Response<crate::protos::auth::v1::RevokeSshKeyResponse>, Status> {
53 request.extensions_mut().insert(RequestMeta {
54 request_id: Uuid::now_v7().to_string(),
55 caller_service: "kenapi".to_owned(),
56 });
43 request.extensions_mut().insert(RequestMeta::internal());
5744 self.api.revoke_ssh_key(request).await
5845 }
5946}
Modified lib/auth/src/internal/cache_maintenance.rs lib/auth/src/internal/cache_maintenance.rs +1 −1 View lib/auth/src/internal/cache_maintenance.rs
@@ -10,8 +10,8 @@
1010
1111use crate::AuthSvc;
1212use crate::errors::AuthError;
13use crate::handlers::identity::me::types::MeResponse;
1413use crate::internal::{context, error, store};
14use crate::me::types::MeResponse;
1515
1616const USER_SCOPED_PREFIXES: [&str; 3] = ["auth:me:", "auth:admin:", "auth:rate:mfa:"];
1717
Modified lib/auth/src/internal/context.rs lib/auth/src/internal/context.rs +7 −0 View lib/auth/src/internal/context.rs
@@ -30,6 +30,13 @@
3030}
3131
3232impl RequestMeta {
33 pub fn internal() -> Self {
34 Self {
35 request_id: Uuid::now_v7().to_string(),
36 caller_service: "kenapi".to_owned(),
37 }
38 }
39
3340 pub fn request_id(&self) -> Option<&str> {
3441 if self.request_id.is_empty() {
3542 None
Modified lib/auth/src/internal/identity.rs lib/auth/src/internal/identity.rs +1 −2 View lib/auth/src/internal/identity.rs
@@ -117,8 +117,7 @@
117117 if let Err(err) = store::drop_user_session_states(self.svc.cache(), user.id, keep).await {
118118 tracing::warn!(%err, user_id = user.id, "failed to drop revoked sessions");
119119 }
120 crate::handlers::identity::me::service::invalidate_me_cache(self.svc.cache(), user.id)
121 .await;
120 crate::me::service::invalidate_me_cache(self.svc.cache(), user.id).await;
122121
123122 store::audit(
124123 db,
Modified lib/auth/src/internal/organization.rs lib/auth/src/internal/organization.rs +10 −18 View lib/auth/src/internal/organization.rs
@@ -9,12 +9,6 @@
99
1010use crate::AuthSvc;
1111use crate::errors::{AuthError, AuthResult};
12use crate::handlers::identity::organization::types::{
13 AddMemberBody, CreateOrganizationBody, CreateRoleBody, JoinRequestSummary, JoinRuleSummary,
14 MAX_BATCH_PERMISSIONS, PermissionGrant, SetMemberRoleBody, SetRolePermissionsBody,
15 UpdateBanBody, UpdateMemberBody, UpdateOrganizationBody,
16};
17use crate::handlers::identity::organization::{member, role, service};
1812use crate::internal::{context, convert, error, store};
1913use crate::models::space::{
2014 MessageNotifications as DomainNotifications, OrgJoinMethod as DomainJoin,
@@ -23,6 +17,12 @@
2317 PermissionEffect as DomainEffect, organization_member_permissions, organization_member_roles,
2418 organization_members, organization_roles, organizations,
2519};
20use crate::organization::types::{
21 AddMemberBody, CreateOrganizationBody, CreateRoleBody, JoinRequestSummary, JoinRuleSummary,
22 MAX_BATCH_PERMISSIONS, PermissionGrant, SetMemberRoleBody, SetRolePermissionsBody,
23 UpdateBanBody, UpdateMemberBody, UpdateOrganizationBody,
24};
25use crate::organization::{member, role, service};
2626use crate::protos::auth::internal::v1::{
2727 GetOrganizationRequest as AdminGetOrganizationRequest,
2828 GetOrganizationResponse as AdminGetOrganizationResponse,
@@ -98,9 +98,7 @@
9898 }
9999}
100100
101fn org_info(
102 summary: crate::handlers::identity::organization::types::OrganizationSummary,
103) -> OrganizationInfo {
101fn org_info(summary: crate::organization::types::OrganizationSummary) -> OrganizationInfo {
104102 OrganizationInfo {
105103 id: summary.id.to_string(),
106104 slug: summary.slug,
@@ -126,9 +124,7 @@
126124 }
127125}
128126
129fn member_info(
130 summary: crate::handlers::identity::organization::types::MemberSummary,
131) -> OrganizationMemberInfo {
127fn member_info(summary: crate::organization::types::MemberSummary) -> OrganizationMemberInfo {
132128 OrganizationMemberInfo {
133129 id: summary.id.to_string(),
134130 organization_id: summary.organization_id.to_string(),
@@ -163,9 +159,7 @@
163159 }
164160}
165161
166fn role_info(
167 summary: crate::handlers::identity::organization::types::RoleSummary,
168) -> OrganizationRoleInfo {
162fn role_info(summary: crate::organization::types::RoleSummary) -> OrganizationRoleInfo {
169163 OrganizationRoleInfo {
170164 id: summary.id.to_string(),
171165 organization_id: summary.organization_id.to_string(),
@@ -195,9 +189,7 @@
195189 }
196190}
197191
198fn invite_info(
199 summary: crate::handlers::identity::organization::types::InviteSummary,
200) -> OrganizationInviteInfo {
192fn invite_info(summary: crate::organization::types::InviteSummary) -> OrganizationInviteInfo {
201193 OrganizationInviteInfo {
202194 id: summary.id.to_string(),
203195 organization_id: summary.organization_id.to_string(),
Modified lib/auth/src/internal/session.rs lib/auth/src/internal/session.rs +2 −10 View lib/auth/src/internal/session.rs
@@ -277,11 +277,7 @@
277277 .exec(self.svc.db().write())
278278 .await
279279 .map_err(|err| error::status(&AuthError::from(err), request_id))?;
280 crate::handlers::identity::me::service::invalidate_me_cache(
281 self.svc.cache(),
282 request.user_id,
283 )
284 .await;
280 crate::me::service::invalidate_me_cache(self.svc.cache(), request.user_id).await;
285281 security_version = store::load_user(self.svc.db(), request.user_id)
286282 .await
287283 .map_err(|err| error::status(&err, request_id))?
@@ -336,11 +332,7 @@
336332 revoked_sessions = revoked.rows_affected as u32;
337333 }
338334
339 crate::handlers::identity::me::service::invalidate_me_cache(
340 self.svc.cache(),
341 request.user_id,
342 )
343 .await;
335 crate::me::service::invalidate_me_cache(self.svc.cache(), request.user_id).await;
344336 store::audit(
345337 self.svc.db(),
346338 request.user_id,
Modified lib/auth/src/internal/ssh_key.rs lib/auth/src/internal/ssh_key.rs +3 −7 View lib/auth/src/internal/ssh_key.rs
@@ -109,7 +109,7 @@
109109 request_id,
110110 ));
111111 }
112 crate::handlers::identity::ssh_key::service::parse_public_key(public_key)
112 crate::ssh_key::service::parse_public_key(public_key)
113113 .map_err(|err| error::status(&err, request_id))?
114114 .1
115115 } else {
@@ -166,7 +166,7 @@
166166 .svc
167167 .add_ssh_key_for_user(
168168 request.user_id,
169 crate::handlers::identity::ssh_key::types::SshKeyAddRequest {
169 crate::ssh_key::types::SshKeyAddRequest {
170170 name: request.name,
171171 description: Some(request.description).filter(|text| !text.is_empty()),
172172 public_key: request.public_key,
@@ -313,11 +313,7 @@
313313 .map_err(|err| error::status(&AuthError::from(err), request_id))?;
314314
315315 if request.bump_security_version {
316 crate::handlers::identity::me::service::invalidate_me_cache(
317 self.svc.cache(),
318 request.user_id,
319 )
320 .await;
316 crate::me::service::invalidate_me_cache(self.svc.cache(), request.user_id).await;
321317 }
322318
323319 if let Err(err) = store::audit(
Added lib/auth/src/me/mod.rs lib/auth/src/me/mod.rs +5 −0 View lib/auth/src/me/mod.rs
@@ -0,0 +1,5 @@
1pub mod service;
2pub mod types;
3
4pub use service::{invalidate_me_cache, me_cache_keys};
5pub use types::MeResponse;
Added lib/auth/src/me/service.rs lib/auth/src/me/service.rs +75 −0 View lib/auth/src/me/service.rs
@@ -0,0 +1,75 @@
1use std::time::Duration;
2
3use crate::models::users::users;
4use crate::session::Session;
5use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
6
7use crate::AuthSvc;
8use crate::errors::{AuthError, AuthResult};
9use crate::me::types::MeResponse;
10
11pub const ME_CACHE_TTL_SECS: u64 = 300;
12const ME_COUNT_TTL_SECS: u64 = 600;
13const ME_REFRESH_EVERY: i64 = 5;
14
15pub fn me_cache_keys(user_id: i64) -> (String, String) {
16 (
17 format!("auth:me:{user_id}"),
18 format!("auth:me:count:{user_id}"),
19 )
20}
21
22pub async fn invalidate_me_cache(cache: &crate::cache::Cache, user_id: i64) {
23 let (cache_key, count_key) = me_cache_keys(user_id);
24 let dropped = async {
25 cache.delete(&cache_key).await?;
26 cache.delete(&count_key).await?;
27 Ok::<(), crate::cache::CacheError>(())
28 }
29 .await;
30 if let Err(error) = dropped {
31 tracing::warn!(%error, user_id, "failed to drop the me cache");
32 }
33}
34
35impl AuthSvc {
36 #[tracing::instrument(skip_all, fields(request_id = %_request_id, client_ip = ?session.client_ip().ok().flatten()))]
37 pub async fn me(&self, session: Session, _request_id: String) -> AuthResult<MeResponse> {
38 let user_id = self
39 .require_session(&session, Some(_request_id.as_str()))
40 .await?;
41 self.me_for_user(user_id).await
42 }
43
44 pub(crate) async fn me_for_user(&self, user_id: i64) -> AuthResult<MeResponse> {
45 let (cache_key, count_key) = me_cache_keys(user_id);
46
47 let count = self.cache().incr(&count_key).await?;
48 if count == 1 {
49 let _ = self
50 .cache()
51 .expire(&count_key, Duration::from_secs(ME_COUNT_TTL_SECS))
52 .await;
53 }
54 if count % ME_REFRESH_EVERY != 0
55 && let Some(cached) = self.cache().get_json::<MeResponse>(&cache_key).await?
56 {
57 return Ok(cached);
58 }
59
60 let user = users::Entity::find_by_id(user_id)
61 .filter(users::Column::DeletedAt.is_null())
62 .one(self.db().read())
63 .await?
64 .ok_or(AuthError::Unauthenticated)?;
65
66 let me = MeResponse::from(user);
67
68 let _ = self
69 .cache()
70 .set_json_ex(&cache_key, &me, Duration::from_secs(ME_CACHE_TTL_SECS))
71 .await;
72
73 Ok(me)
74 }
75}
Added lib/auth/src/me/types.rs lib/auth/src/me/types.rs +33 −0 View lib/auth/src/me/types.rs
@@ -0,0 +1,33 @@
1use crate::models::users::users;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, ToSchema)]
7pub struct MeResponse {
8 pub user_id: i64,
9 pub username: String,
10 pub displayname: Option<String>,
11 pub email: String,
12 pub is_admin: bool,
13 pub avatar_url: Option<String>,
14 pub email_verified_at: Option<DateTime<Utc>>,
15 pub created_at: DateTime<Utc>,
16 pub version: i32,
17}
18
19impl From<users::Model> for MeResponse {
20 fn from(user: users::Model) -> Self {
21 Self {
22 user_id: user.id,
23 username: user.username,
24 displayname: user.displayname,
25 email: user.email,
26 is_admin: user.is_admin,
27 avatar_url: None,
28 email_verified_at: user.email_verified_at,
29 created_at: user.created_at,
30 version: user.version,
31 }
32 }
33}
Added lib/auth/src/organization/error.rs lib/auth/src/organization/error.rs +22 −0 View lib/auth/src/organization/error.rs
@@ -0,0 +1,22 @@
1use crate::errors::AuthError;
2
3pub(crate) fn reason_error(reason: &'static str, permission: &str) -> AuthError {
4 match reason {
5 "org_inactive" => AuthError::OrganizationInactive,
6 "member_banned" => AuthError::OrganizationMemberBanned,
7 "not_member" | "member_left" => AuthError::Forbidden,
8 _ => AuthError::ScopeInsufficient {
9 required: permission.to_owned(),
10 },
11 }
12}
13
14pub(crate) fn map_insert_conflict(error: sea_orm::DbErr) -> AuthError {
15 if let sea_orm::DbErr::Query(sea_orm::RuntimeErr::SqlxError(source)) = &error
16 && let Some(code) = source.as_database_error().and_then(|db| db.code())
17 && code.as_ref() == "23505"
18 {
19 return AuthError::OrganizationSlugTaken;
20 }
21 AuthError::from(error)
22}
Added lib/auth/src/organization/invite.rs lib/auth/src/organization/invite.rs +469 −0 View lib/auth/src/organization/invite.rs
@@ -0,0 +1,469 @@
1use chrono::Utc;
2use sea_orm::entity::Set;
3use sea_orm::sea_query::{Expr, ExprTrait};
4use sea_orm::{
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder,
6 QuerySelect, TransactionTrait,
7};
8use uuid::Uuid;
9
10use crate::AuthSvc;
11use crate::errors::{AuthError, AuthResult};
12use crate::events::label;
13use crate::models::space::{
14 OrgJoinMethod, OrgMemberRole, OrganizationStatus, organization_invite_redemptions,
15 organization_invites, organization_roles,
16};
17use crate::organization::member;
18use crate::organization::service::{bump_version, normalize_reason};
19use crate::organization::types::{
20 DEFAULT_INVITE_TTL_SECS, InviteSummary, SYSTEM_ROLE_MEMBER, SYSTEM_ROLE_OWNER,
21};
22use crate::share::crypto;
23
24impl AuthSvc {
25 #[allow(clippy::too_many_arguments)]
26 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
27 pub async fn create_invite(
28 &self,
29 actor_user_id: i64,
30 organization_id: Uuid,
31 role_id: Option<Uuid>,
32 target_user_id: Option<i64>,
33 max_uses: i32,
34 ttl_secs: Option<u64>,
35 request_id: Option<&str>,
36 ) -> AuthResult<(InviteSummary, String)> {
37 self.create_invite_with_extras(
38 actor_user_id,
39 organization_id,
40 role_id,
41 target_user_id,
42 max_uses,
43 ttl_secs,
44 None,
45 &[],
46 request_id,
47 )
48 .await
49 }
50
51 /// 邮箱型邀请与额外角色必须在同一个事务里写入,否则邀请已经可用而扩展字段丢失。
52 #[allow(clippy::too_many_arguments)]
53 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
54 pub async fn create_invite_with_extras(
55 &self,
56 actor_user_id: i64,
57 organization_id: Uuid,
58 role_id: Option<Uuid>,
59 target_user_id: Option<i64>,
60 max_uses: i32,
61 ttl_secs: Option<u64>,
62 email: Option<String>,
63 extra_role_ids: &[Uuid],
64 request_id: Option<&str>,
65 ) -> AuthResult<(InviteSummary, String)> {
66 self.require_permission(actor_user_id, organization_id, "invite.create", request_id)
67 .await?;
68 let organization = self.load_organization(organization_id).await?;
69 if organization.status != OrganizationStatus::Active {
70 return Err(AuthError::OrganizationInactive);
71 }
72 if max_uses < 0 {
73 return Err(AuthError::MalformedRequest);
74 }
75 if let Some(role_id) = role_id {
76 let role = organization_roles::Entity::find_by_id(role_id)
77 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
78 .one(self.db().read())
79 .await?
80 .ok_or(AuthError::OrganizationRoleNotFound)?;
81 if role.key == SYSTEM_ROLE_OWNER {
82 return Err(AuthError::Forbidden);
83 }
84 }
85 if !extra_role_ids.is_empty() {
86 // 额外角色等于提前授予权限,与主动分配角色同等审查。
87 self.require_permission(actor_user_id, organization_id, "role.assign", request_id)
88 .await?;
89 for extra_role_id in extra_role_ids {
90 let role = organization_roles::Entity::find_by_id(*extra_role_id)
91 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
92 .one(self.db().read())
93 .await?
94 .ok_or(AuthError::OrganizationRoleNotFound)?;
95 if role.key == SYSTEM_ROLE_OWNER {
96 return Err(AuthError::Forbidden);
97 }
98 }
99 }
100 let email = email
101 .map(|value| crate::share::validate::validate_email(&value))
102 .transpose()?;
103 let method = match target_user_id {
104 Some(user_id) => {
105 crate::models::users::users::Entity::find_by_id(user_id)
106 .filter(crate::models::users::users::Column::DeletedAt.is_null())
107 .one(self.db().read())
108 .await?
109 .ok_or(AuthError::UserNotFound)?;
110 OrgJoinMethod::InviteTargeted
111 }
112 None => OrgJoinMethod::InviteLink,
113 };
114 let method = if email.is_some() {
115 OrgJoinMethod::InviteEmail
116 } else {
117 method
118 };
119 let extra_role_ids = if extra_role_ids.is_empty() {
120 None
121 } else {
122 Some(
123 extra_role_ids
124 .iter()
125 .map(|id| id.to_string())
126 .collect::<Vec<_>>()
127 .join(","),
128 )
129 };
130
131 // 明文令牌只在这里出现一次,库里只留 sha256。
132 let token = crypto::random_b64url(32);
133 let token_hash = crypto::sha256_hex(token.as_bytes());
134 let ttl = ttl_secs
135 .unwrap_or(DEFAULT_INVITE_TTL_SECS)
136 .clamp(60, 90 * 24 * 60 * 60);
137 let expires_at = Some(Utc::now() + chrono::Duration::seconds(ttl as i64));
138
139 let txn = self.db().write().begin().await?;
140 let row = organization_invites::ActiveModel {
141 organization_id: Set(organization_id),
142 method: Set(method),
143 token_hash: Set(token_hash),
144 role_id: Set(role_id),
145 target_user_id: Set(target_user_id),
146 created_by: Set(actor_user_id),
147 max_uses: Set(max_uses),
148 uses: Set(0),
149 email: Set(email),
150 extra_role_ids: Set(extra_role_ids),
151 expires_at: Set(expires_at),
152 ..Default::default()
153 }
154 .insert(&txn)
155 .await?;
156 bump_version(&txn, organization_id).await?;
157 self.emit_invite(
158 &txn,
159 organization_id,
160 actor_user_id,
161 serde_json::json!({
162 "id": row.id,
163 "method": match row.method {
164 OrgJoinMethod::InviteLink => "invite_link",
165 OrgJoinMethod::InviteTargeted => "invite_targeted",
166 OrgJoinMethod::InviteEmail => "invite_email",
167 OrgJoinMethod::Direct => "direct",
168 },
169 "target_user_id": target_user_id,
170 "max_uses": max_uses,
171 "request_id": request_id,
172 }),
173 request_id,
174 )
175 .await?;
176 txn.commit().await?;
177
178 self.invalidate_org(organization_id).await?;
179 let summary = invite_summary(&row);
180 Ok((summary, token))
181 }
182
183 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
184 pub async fn list_invites(
185 &self,
186 actor_user_id: i64,
187 organization_id: Uuid,
188 include_revoked: bool,
189 limit: u64,
190 offset: u64,
191 request_id: Option<&str>,
192 ) -> AuthResult<Vec<InviteSummary>> {
193 self.require_permission(actor_user_id, organization_id, "invite.view", request_id)
194 .await?;
195 let mut query = organization_invites::Entity::find()
196 .filter(organization_invites::Column::OrganizationId.eq(organization_id));
197 if !include_revoked {
198 query = query.filter(organization_invites::Column::RevokedAt.is_null());
199 }
200 let rows = query
201 .order_by_desc(organization_invites::Column::CreatedAt)
202 .limit(limit)
203 .offset(offset)
204 .all(self.db().read())
205 .await?;
206 Ok(rows.iter().map(invite_summary).collect())
207 }
208
209 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, invite_id = %invite_id, request_id = %request_id.unwrap_or_default()))]
210 pub async fn revoke_invite(
211 &self,
212 actor_user_id: i64,
213 organization_id: Uuid,
214 invite_id: Uuid,
215 reason: Option<&str>,
216 request_id: Option<&str>,
217 ) -> AuthResult<bool> {
218 let reason = normalize_reason(reason)?;
219 let invite = organization_invites::Entity::find_by_id(invite_id)
220 .filter(organization_invites::Column::OrganizationId.eq(organization_id))
221 .one(self.db().read())
222 .await?
223 .ok_or(AuthError::OrganizationInviteNotFound)?;
224 if invite.target_user_id != Some(actor_user_id) {
225 self.require_permission(actor_user_id, organization_id, "invite.revoke", request_id)
226 .await?;
227 }
228 if invite.revoked_at.is_some() {
229 return Ok(false);
230 }
231
232 let txn = self.db().write().begin().await?;
233 organization_invites::Entity::update_many()
234 .col_expr(
235 organization_invites::Column::RevokedAt,
236 Expr::value(Utc::now()),
237 )
238 .filter(organization_invites::Column::Id.eq(invite_id))
239 .exec(&txn)
240 .await?;
241 let version = bump_version(&txn, organization_id).await?;
242 self.emit_invite(
243 &txn,
244 organization_id,
245 actor_user_id,
246 serde_json::json!({
247 "id": invite_id,
248 "action": "revoke",
249 "reason": &reason,
250 "version": version,
251 "request_id": request_id,
252 }),
253 request_id,
254 )
255 .await?;
256 txn.commit().await?;
257
258 self.invalidate_org(organization_id).await?;
259 Ok(true)
260 }
261
262 /// 兑换邀请。整个过程在一个事务里完成,uses 递增用条件更新防止并发超发:
263 /// `UPDATE ... WHERE uses < max_uses` 影响 0 行即视为已用尽。
264 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, request_id = %request_id.unwrap_or_default()))]
265 pub async fn redeem_invite(
266 &self,
267 actor_user_id: i64,
268 token: &str,
269 request_id: Option<&str>,
270 ) -> AuthResult<(Uuid, Uuid, bool)> {
271 let trimmed = token.trim();
272 if trimmed.is_empty() || trimmed.len() > 128 {
273 return Err(AuthError::OrganizationInviteNotFound);
274 }
275 let token_hash = crypto::sha256_hex(trimmed.as_bytes());
276 let invite = organization_invites::Entity::find()
277 .filter(organization_invites::Column::TokenHash.eq(token_hash))
278 .one(self.db().read())
279 .await?
280 .ok_or(AuthError::OrganizationInviteNotFound)?;
281 if invite.revoked_at.is_some() {
282 return Err(AuthError::OrganizationInviteExhausted);
283 }
284 if let Some(expires_at) = invite.expires_at
285 && expires_at <= Utc::now()
286 {
287 return Err(AuthError::OrganizationInviteExpired);
288 }
289 if let Some(target_user_id) = invite.target_user_id
290 && target_user_id != actor_user_id
291 {
292 return Err(AuthError::Forbidden);
293 }
294 let organization = self.load_organization(invite.organization_id).await?;
295 if organization.status != OrganizationStatus::Active {
296 return Err(AuthError::OrganizationInactive);
297 }
298 let actor = crate::models::users::users::Entity::find_by_id(actor_user_id)
299 .filter(crate::models::users::users::Column::DeletedAt.is_null())
300 .one(self.db().read())
301 .await?
302 .ok_or(AuthError::UserNotFound)?;
303 if let Some(email) = invite
304 .email
305 .as_deref()
306 .map(str::trim)
307 .filter(|value| !value.is_empty())
308 && !actor.email.eq_ignore_ascii_case(email)
309 {
310 return Err(AuthError::Forbidden);
311 }
312
313 let txn = self.db().write().begin().await?;
314 let mut consume = organization_invites::Entity::update_many()
315 .col_expr(
316 organization_invites::Column::Uses,
317 Expr::col(organization_invites::Column::Uses).add(1),
318 )
319 .filter(organization_invites::Column::Id.eq(invite.id));
320 if invite.max_uses > 0 {
321 consume = consume.filter(organization_invites::Column::Uses.lt(invite.max_uses));
322 }
323 let consumed = consume.exec(&txn).await?;
324 if consumed.rows_affected == 0 {
325 return Err(AuthError::OrganizationInviteExhausted);
326 }
327
328 let role = match invite.role_id {
329 Some(role_id) => {
330 organization_roles::Entity::find_by_id(role_id)
331 .filter(organization_roles::Column::OrganizationId.eq(invite.organization_id))
332 .one(&txn)
333 .await?
334 }
335 None => self.default_role_row(&txn, invite.organization_id).await?,
336 };
337
338 let method = match invite.method {
339 OrgJoinMethod::InviteTargeted => OrgJoinMethod::InviteTargeted,
340 OrgJoinMethod::InviteEmail => OrgJoinMethod::InviteEmail,
341 _ => OrgJoinMethod::InviteLink,
342 };
343 let already = member::find_any(&txn, invite.organization_id, actor_user_id).await?;
344 let (row, joined) = member::upsert_join(
345 &txn,
346 invite.organization_id,
347 actor_user_id,
348 OrgMemberRole::Member,
349 method,
350 Some(invite.created_by),
351 Some(invite.id),
352 )
353 .await?;
354 if let Some(role) = role
355 && role.key != SYSTEM_ROLE_OWNER
356 {
357 crate::organization::role::bind_role(&txn, row.id, role.id, invite.created_by).await?;
358 }
359 if let Some(extra) = invite.extra_role_ids.as_deref() {
360 for raw in extra.split(',') {
361 let Ok(role_id) = Uuid::parse_str(raw.trim()) else {
362 continue;
363 };
364 let Some(role) = organization_roles::Entity::find_by_id(role_id)
365 .filter(organization_roles::Column::OrganizationId.eq(invite.organization_id))
366 .one(&txn)
367 .await?
368 else {
369 continue;
370 };
371 if role.key == SYSTEM_ROLE_OWNER || role.key == SYSTEM_ROLE_MEMBER {
372 continue;
373 }
374 crate::organization::role::bind_role(&txn, row.id, role.id, invite.created_by)
375 .await?;
376 }
377 }
378 let redeemed = already.is_none();
379 if redeemed {
380 let existing = organization_invite_redemptions::Entity::find()
381 .filter(organization_invite_redemptions::Column::InviteId.eq(invite.id))
382 .filter(organization_invite_redemptions::Column::UserId.eq(actor_user_id))
383 .one(&txn)
384 .await?;
385 if existing.is_none() {
386 organization_invite_redemptions::ActiveModel {
387 invite_id: Set(invite.id),
388 user_id: Set(actor_user_id),
389 organization_id: Set(invite.organization_id),
390 ..Default::default()
391 }
392 .insert(&txn)
393 .await?;
394 }
395 }
396 let version = bump_version(&txn, invite.organization_id).await?;
397 self.emit_invite(
398 &txn,
399 invite.organization_id,
400 actor_user_id,
401 serde_json::json!({
402 "id": invite.id,
403 "action": "redeem",
404 "user_id": actor_user_id,
405 "joined": joined,
406 "version": version,
407 "request_id": request_id,
408 }),
409 request_id,
410 )
411 .await?;
412 txn.commit().await?;
413
414 self.invalidate_org(invite.organization_id).await?;
415 Ok((invite.organization_id, row.id, joined))
416 }
417
418 pub(crate) async fn default_role_row<C: ConnectionTrait>(
419 &self,
420 db: &C,
421 organization_id: Uuid,
422 ) -> AuthResult<Option<organization_roles::Model>> {
423 Ok(organization_roles::Entity::find()
424 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
425 .filter(organization_roles::Column::DefaultRole.eq(true))
426 .order_by_asc(organization_roles::Column::Rank)
427 .one(db)
428 .await?)
429 }
430
431 /// 邀请事件与业务写入同事务入队:提交时一起可见,回滚时一起消失。
432 pub(crate) async fn emit_invite(
433 &self,
434 txn: &sea_orm::DatabaseTransaction,
435 organization_id: Uuid,
436 actor_user_id: i64,
437 payload: serde_json::Value,
438 _request_id: Option<&str>,
439 ) -> AuthResult<()> {
440 let labels = crate::events::Labels::new()
441 .with(label::ORGANIZATION_ID, organization_id)
442 .with(label::ACTOR_USER_ID, actor_user_id);
443 self.emit_tx(
444 txn,
445 label::ORGANIZATION_INVITE_CREATE,
446 labels,
447 payload,
448 None,
449 )
450 .await
451 }
452}
453
454pub(crate) fn invite_summary(row: &organization_invites::Model) -> InviteSummary {
455 InviteSummary {
456 id: row.id,
457 organization_id: row.organization_id,
458 method: row.method,
459 role_id: row.role_id,
460 target_user_id: row.target_user_id,
461 created_by: row.created_by,
462 max_uses: row.max_uses,
463 uses: row.uses,
464 expires_at: row.expires_at,
465 revoked_at: row.revoked_at,
466 created_at: row.created_at,
467 email: row.email.clone(),
468 }
469}
Added lib/auth/src/organization/join.rs lib/auth/src/organization/join.rs +483 −0 View lib/auth/src/organization/join.rs
@@ -0,0 +1,483 @@
1use chrono::Utc;
2use sea_orm::sea_query::Expr;
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
5 QuerySelect, Set, TransactionTrait,
6};
7use uuid::Uuid;
8
9use crate::models::space::{
10 OrgJoinMethod, OrgMemberRole, OrganizationJoinRequestStatus, organization_join_requests,
11 organization_join_rules, organization_members, organization_roles,
12};
13use crate::models::users::users;
14use crate::organization::types::{
15 AddMemberBody, JoinRequestSummary, JoinRuleSummary, SYSTEM_ROLE_MEMBER, SYSTEM_ROLE_OWNER,
16};
17use crate::organization::{member, service};
18use crate::{AuthError, AuthResult, AuthSvc};
19
20fn rule_summary(row: &organization_join_rules::Model) -> JoinRuleSummary {
21 JoinRuleSummary {
22 id: row.id,
23 organization_id: row.organization_id,
24 role_id: row.role_id,
25 trigger: row.trigger.clone(),
26 matcher: row.matcher.clone(),
27 enabled: row.enabled,
28 created_at: row.created_at,
29 updated_at: row.updated_at,
30 }
31}
32
33impl AuthSvc {
34 async fn join_request_summary(
35 &self,
36 row: &organization_join_requests::Model,
37 ) -> AuthResult<JoinRequestSummary> {
38 let user = users::Entity::find_by_id(row.user_id)
39 .one(self.db().read())
40 .await?;
41 Ok(JoinRequestSummary {
42 id: row.id,
43 organization_id: row.organization_id,
44 user_id: row.user_id,
45 username: user
46 .as_ref()
47 .map(|value| value.username.clone())
48 .unwrap_or_default(),
49 displayname: user.and_then(|value| value.displayname),
50 status: row.status,
51 note: row.note.clone(),
52 decided_by: row.decided_by,
53 decided_at: row.decided_at,
54 created_at: row.created_at,
55 updated_at: row.updated_at,
56 })
57 }
58
59 // 加入规则的可选字段本身就多,参数表与 HTTP 请求体一一对应,拆结构体只会增加噪音。
60 #[allow(clippy::too_many_arguments)]
61 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
62 pub async fn create_join_rule(
63 &self,
64 actor_user_id: i64,
65 organization_id: Uuid,
66 role_id: Uuid,
67 trigger: String,
68 matcher: String,
69 enabled: bool,
70 request_id: Option<&str>,
71 ) -> AuthResult<JoinRuleSummary> {
72 self.require_permission(actor_user_id, organization_id, "role.update", request_id)
73 .await?;
74 let trigger = trigger.trim().to_string();
75 let matcher = matcher.trim().to_string();
76 if trigger.is_empty() || matcher.is_empty() {
77 return Err(AuthError::MalformedRequest);
78 }
79 let role = organization_roles::Entity::find_by_id(role_id)
80 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
81 .one(self.db().read())
82 .await?
83 .ok_or(AuthError::OrganizationRoleNotFound)?;
84 let row = organization_join_rules::ActiveModel {
85 organization_id: Set(organization_id),
86 role_id: Set(role.id),
87 trigger: Set(trigger),
88 matcher: Set(matcher),
89 enabled: Set(enabled),
90 ..Default::default()
91 }
92 .insert(self.db().write())
93 .await?;
94 service::bump_version(self.db().write(), organization_id).await?;
95 Ok(rule_summary(&row))
96 }
97
98 // 同上:可选字段多,保持与请求体一致的参数顺序。
99 #[allow(clippy::too_many_arguments)]
100 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, rule_id = %rule_id, request_id = %request_id.unwrap_or_default()))]
101 pub async fn update_join_rule(
102 &self,
103 actor_user_id: i64,
104 organization_id: Uuid,
105 rule_id: Uuid,
106 role_id: Option<Uuid>,
107 trigger: Option<String>,
108 matcher: Option<String>,
109 enabled: Option<bool>,
110 request_id: Option<&str>,
111 ) -> AuthResult<JoinRuleSummary> {
112 self.require_permission(actor_user_id, organization_id, "role.update", request_id)
113 .await?;
114 let row = organization_join_rules::Entity::find_by_id(rule_id)
115 .filter(organization_join_rules::Column::OrganizationId.eq(organization_id))
116 .one(self.db().read())
117 .await?
118 .ok_or(AuthError::OrganizationNotFound)?;
119 let mut active: organization_join_rules::ActiveModel = row.into();
120 if let Some(role_id) = role_id {
121 let role = organization_roles::Entity::find_by_id(role_id)
122 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
123 .one(self.db().read())
124 .await?
125 .ok_or(AuthError::OrganizationRoleNotFound)?;
126 active.role_id = Set(role.id);
127 }
128 if let Some(trigger) = trigger {
129 let trigger = trigger.trim().to_string();
130 if trigger.is_empty() {
131 return Err(AuthError::MalformedRequest);
132 }
133 active.trigger = Set(trigger);
134 }
135 if let Some(matcher) = matcher {
136 let matcher = matcher.trim().to_string();
137 if matcher.is_empty() {
138 return Err(AuthError::MalformedRequest);
139 }
140 active.matcher = Set(matcher);
141 }
142 if let Some(enabled) = enabled {
143 active.enabled = Set(enabled);
144 }
145 let updated = active.update(self.db().write()).await?;
146 service::bump_version(self.db().write(), organization_id).await?;
147 Ok(rule_summary(&updated))
148 }
149
150 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, rule_id = %rule_id, request_id = %request_id.unwrap_or_default()))]
151 pub async fn delete_join_rule(
152 &self,
153 actor_user_id: i64,
154 organization_id: Uuid,
155 rule_id: Uuid,
156 request_id: Option<&str>,
157 ) -> AuthResult<bool> {
158 self.require_permission(actor_user_id, organization_id, "role.update", request_id)
159 .await?;
160 let deleted = organization_join_rules::Entity::delete_many()
161 .filter(organization_join_rules::Column::Id.eq(rule_id))
162 .filter(organization_join_rules::Column::OrganizationId.eq(organization_id))
163 .exec(self.db().write())
164 .await?;
165 if deleted.rows_affected > 0 {
166 service::bump_version(self.db().write(), organization_id).await?;
167 }
168 Ok(deleted.rows_affected > 0)
169 }
170
171 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
172 pub async fn list_join_rules(
173 &self,
174 actor_user_id: i64,
175 organization_id: Uuid,
176 request_id: Option<&str>,
177 ) -> AuthResult<Vec<JoinRuleSummary>> {
178 self.require_permission(actor_user_id, organization_id, "role.view", request_id)
179 .await?;
180 let rows = organization_join_rules::Entity::find()
181 .filter(organization_join_rules::Column::OrganizationId.eq(organization_id))
182 .order_by_asc(organization_join_rules::Column::CreatedAt)
183 .all(self.db().read())
184 .await?;
185 Ok(rows.iter().map(rule_summary).collect())
186 }
187
188 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
189 pub async fn create_join_request(
190 &self,
191 actor_user_id: i64,
192 organization_id: Uuid,
193 note: Option<String>,
194 request_id: Option<&str>,
195 ) -> AuthResult<JoinRequestSummary> {
196 let organization = self.load_organization(organization_id).await?;
197 if organization.status != crate::models::space::OrganizationStatus::Active {
198 return Err(AuthError::OrganizationInactive);
199 }
200 if let Some(existing) =
201 member::find_any(self.db().read(), organization_id, actor_user_id).await?
202 {
203 if existing.banned_at.is_some() {
204 return Err(AuthError::OrganizationMemberBanned);
205 }
206 if existing.left_at.is_none() {
207 return Err(AuthError::OrganizationMemberExists);
208 }
209 }
210 if let Some(pending) = organization_join_requests::Entity::find()
211 .filter(organization_join_requests::Column::OrganizationId.eq(organization_id))
212 .filter(organization_join_requests::Column::UserId.eq(actor_user_id))
213 .filter(
214 organization_join_requests::Column::Status
215 .eq(OrganizationJoinRequestStatus::Pending),
216 )
217 .one(self.db().read())
218 .await?
219 {
220 let mut active: organization_join_requests::ActiveModel = pending.into();
221 active.note = Set(note);
222 let updated = active.update(self.db().write()).await?;
223 return self.join_request_summary(&updated).await;
224 }
225 let row = organization_join_requests::ActiveModel {
226 organization_id: Set(organization_id),
227 user_id: Set(actor_user_id),
228 status: Set(OrganizationJoinRequestStatus::Pending),
229 note: Set(note),
230 ..Default::default()
231 }
232 .insert(self.db().write())
233 .await?;
234 self.join_request_summary(&row).await
235 }
236
237 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
238 pub async fn list_join_requests(
239 &self,
240 actor_user_id: i64,
241 organization_id: Uuid,
242 status: Option<OrganizationJoinRequestStatus>,
243 limit: u64,
244 offset: u64,
245 request_id: Option<&str>,
246 ) -> AuthResult<(Vec<JoinRequestSummary>, u64)> {
247 self.require_permission(actor_user_id, organization_id, "member.view", request_id)
248 .await?;
249 let mut select = organization_join_requests::Entity::find()
250 .filter(organization_join_requests::Column::OrganizationId.eq(organization_id));
251 if let Some(status) = status {
252 select = select.filter(organization_join_requests::Column::Status.eq(status));
253 }
254 let total = select.clone().count(self.db().read()).await?;
255 let rows = select
256 .order_by_desc(organization_join_requests::Column::CreatedAt)
257 .limit(limit)
258 .offset(offset)
259 .all(self.db().read())
260 .await?;
261 let mut out = Vec::with_capacity(rows.len());
262 for row in rows {
263 out.push(self.join_request_summary(&row).await?);
264 }
265 Ok((out, total))
266 }
267
268 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, join_request_id = %join_request_id, request_id = %request_id.unwrap_or_default()))]
269 pub async fn withdraw_join_request(
270 &self,
271 actor_user_id: i64,
272 organization_id: Uuid,
273 join_request_id: Uuid,
274 request_id: Option<&str>,
275 ) -> AuthResult<JoinRequestSummary> {
276 let row = organization_join_requests::Entity::find_by_id(join_request_id)
277 .filter(organization_join_requests::Column::OrganizationId.eq(organization_id))
278 .one(self.db().read())
279 .await?
280 .ok_or(AuthError::OrganizationMemberNotFound)?;
281 if row.user_id != actor_user_id {
282 return Err(AuthError::Forbidden);
283 }
284 if row.status != OrganizationJoinRequestStatus::Pending {
285 return Err(AuthError::Forbidden);
286 }
287 let mut active: organization_join_requests::ActiveModel = row.into();
288 active.status = Set(OrganizationJoinRequestStatus::Withdrawn);
289 let updated = active.update(self.db().write()).await?;
290 self.join_request_summary(&updated).await
291 }
292
293 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, join_request_id = %join_request_id, request_id = %request_id.unwrap_or_default()))]
294 async fn finish_join_request(
295 &self,
296 actor_user_id: i64,
297 organization_id: Uuid,
298 join_request_id: Uuid,
299 status: OrganizationJoinRequestStatus,
300 note: Option<String>,
301 request_id: Option<&str>,
302 ) -> AuthResult<JoinRequestSummary> {
303 self.require_permission(actor_user_id, organization_id, "member.add", request_id)
304 .await?;
305 let row = organization_join_requests::Entity::find_by_id(join_request_id)
306 .filter(organization_join_requests::Column::OrganizationId.eq(organization_id))
307 .one(self.db().read())
308 .await?
309 .ok_or(AuthError::OrganizationMemberNotFound)?;
310 if row.status != OrganizationJoinRequestStatus::Pending {
311 return Err(AuthError::Forbidden);
312 }
313 if status == OrganizationJoinRequestStatus::Approved {
314 let default_role = organization_roles::Entity::find()
315 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
316 .filter(organization_roles::Column::Key.eq(SYSTEM_ROLE_MEMBER))
317 .one(self.db().read())
318 .await?;
319 self.add_member(
320 actor_user_id,
321 organization_id,
322 AddMemberBody {
323 user_id: row.user_id,
324 role: Some(OrgMemberRole::Member),
325 role_keys: default_role
326 .filter(|role| role.default_role)
327 .map(|role| vec![role.key])
328 .unwrap_or_default(),
329 },
330 request_id,
331 )
332 .await?;
333 }
334 let mut active: organization_join_requests::ActiveModel = row.into();
335 active.status = Set(status);
336 active.decided_by = Set(Some(actor_user_id));
337 active.decided_at = Set(Some(Utc::now()));
338 if let Some(note) = note {
339 active.note = Set(Some(note));
340 }
341 let updated = active.update(self.db().write()).await?;
342 self.join_request_summary(&updated).await
343 }
344
345 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, join_request_id = %join_request_id, request_id = %request_id.unwrap_or_default()))]
346 pub async fn approve_join_request(
347 &self,
348 actor_user_id: i64,
349 organization_id: Uuid,
350 join_request_id: Uuid,
351 request_id: Option<&str>,
352 ) -> AuthResult<JoinRequestSummary> {
353 self.finish_join_request(
354 actor_user_id,
355 organization_id,
356 join_request_id,
357 OrganizationJoinRequestStatus::Approved,
358 None,
359 request_id,
360 )
361 .await
362 }
363
364 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, join_request_id = %join_request_id, request_id = %request_id.unwrap_or_default()))]
365 pub async fn reject_join_request(
366 &self,
367 actor_user_id: i64,
368 organization_id: Uuid,
369 join_request_id: Uuid,
370 note: Option<String>,
371 request_id: Option<&str>,
372 ) -> AuthResult<JoinRequestSummary> {
373 self.finish_join_request(
374 actor_user_id,
375 organization_id,
376 join_request_id,
377 OrganizationJoinRequestStatus::Rejected,
378 note,
379 request_id,
380 )
381 .await
382 }
383
384 #[tracing::instrument(skip_all, fields(organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
385 pub async fn ensure_bot_member(
386 &self,
387 organization_id: Uuid,
388 user_id: i64,
389 role_keys: Vec<String>,
390 role_ids: Vec<String>,
391 request_id: Option<&str>,
392 ) -> AuthResult<(crate::organization::types::MemberSummary, bool)> {
393 let txn = self.db().write().begin().await?;
394 // 机器人成员的角色上限:OWNER 只能由人类走成员面转移,不能通过内部接口直接塞入。
395 let user = crate::models::users::users::Entity::find_by_id(user_id)
396 .filter(crate::models::users::users::Column::DeletedAt.is_null())
397 .one(&txn)
398 .await?
399 .ok_or(AuthError::UserNotFound)?;
400 if !user.bot {
401 return Err(AuthError::Forbidden);
402 }
403 let (row, created) = member::upsert_join(
404 &txn,
405 organization_id,
406 user_id,
407 OrgMemberRole::Member,
408 OrgJoinMethod::Direct,
409 None,
410 None,
411 )
412 .await?;
413 let mut roles = Vec::new();
414 for key in role_keys {
415 if let Some(role) = organization_roles::Entity::find()
416 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
417 .filter(organization_roles::Column::Key.eq(key))
418 .one(&txn)
419 .await?
420 {
421 if role.key == SYSTEM_ROLE_OWNER {
422 return Err(AuthError::Forbidden);
423 }
424 roles.push(role);
425 }
426 }
427 for id in role_ids {
428 let Ok(id) = Uuid::parse_str(id.trim()) else {
429 continue;
430 };
431 if let Some(role) = organization_roles::Entity::find_by_id(id)
432 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
433 .one(&txn)
434 .await?
435 {
436 if role.key == SYSTEM_ROLE_OWNER {
437 return Err(AuthError::Forbidden);
438 }
439 roles.push(role);
440 }
441 }
442 for role in roles {
443 let existing = crate::models::space::organization_member_roles::Entity::find()
444 .filter(
445 crate::models::space::organization_member_roles::Column::MemberId.eq(row.id),
446 )
447 .filter(crate::models::space::organization_member_roles::Column::RoleId.eq(role.id))
448 .one(&txn)
449 .await?;
450 if existing.is_none() {
451 crate::models::space::organization_member_roles::ActiveModel {
452 member_id: Set(row.id),
453 role_id: Set(role.id),
454 ..Default::default()
455 }
456 .insert(&txn)
457 .await?;
458 }
459 }
460 service::bump_version(&txn, organization_id).await?;
461 txn.commit().await?;
462 let summary = self.member_summary(&row).await?;
463 Ok((summary, created))
464 }
465
466 #[tracing::instrument(skip_all, fields(organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
467 pub async fn touch_member_activity(
468 &self,
469 organization_id: Uuid,
470 user_id: i64,
471 request_id: Option<&str>,
472 ) {
473 let _ = organization_members::Entity::update_many()
474 .col_expr(
475 organization_members::Column::LastActiveAt,
476 Expr::value(Utc::now()),
477 )
478 .filter(organization_members::Column::OrganizationId.eq(organization_id))
479 .filter(organization_members::Column::UserId.eq(user_id))
480 .exec(self.db().write())
481 .await;
482 }
483}
Added lib/auth/src/organization/member.rs lib/auth/src/organization/member.rs +939 −0 View lib/auth/src/organization/member.rs
@@ -0,0 +1,939 @@
1use chrono::Utc;
2use sea_orm::entity::Set;
3use sea_orm::sea_query::{Expr, ExprTrait};
4use sea_orm::{
5 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder,
6 QuerySelect, TransactionTrait,
7};
8use uuid::Uuid;
9
10use crate::AuthSvc;
11use crate::errors::{AuthError, AuthResult};
12use crate::events::label;
13use crate::models::space::{
14 OrgJoinMethod, OrgMemberRole, organization_member_permissions, organization_members,
15};
16use crate::models::users::users;
17use crate::organization::role;
18use crate::organization::service::{bump_version, normalize_reason};
19use crate::organization::types::{
20 AddMemberBody, MAX_ALIAS_AVATAR_URL_LEN, MAX_ALIAS_NAME_LEN, MAX_REASON_LEN, MemberSummary,
21 SetMemberRoleBody, UpdateBanBody, UpdateMemberBody, normalize_alias,
22};
23
24pub(crate) async fn find_any<C: ConnectionTrait>(
25 db: &C,
26 organization_id: Uuid,
27 user_id: i64,
28) -> AuthResult<Option<organization_members::Model>> {
29 Ok(organization_members::Entity::find()
30 .filter(organization_members::Column::OrganizationId.eq(organization_id))
31 .filter(organization_members::Column::UserId.eq(user_id))
32 .one(db)
33 .await?)
34}
35
36pub(crate) async fn find_active<C: ConnectionTrait>(
37 db: &C,
38 organization_id: Uuid,
39 user_id: i64,
40) -> AuthResult<Option<organization_members::Model>> {
41 Ok(organization_members::Entity::find()
42 .filter(organization_members::Column::OrganizationId.eq(organization_id))
43 .filter(organization_members::Column::UserId.eq(user_id))
44 .filter(organization_members::Column::LeftAt.is_null())
45 .filter(organization_members::Column::BannedAt.is_null())
46 .one(db)
47 .await?)
48}
49
50pub(crate) async fn load_by_id<C: ConnectionTrait>(
51 db: &C,
52 organization_id: Uuid,
53 member_id: Uuid,
54) -> AuthResult<organization_members::Model> {
55 organization_members::Entity::find_by_id(member_id)
56 .filter(organization_members::Column::OrganizationId.eq(organization_id))
57 .one(db)
58 .await?
59 .ok_or(AuthError::OrganizationMemberNotFound)
60}
61
62pub(crate) async fn insert_owner<C: ConnectionTrait>(
63 db: &C,
64 organization_id: Uuid,
65 user_id: i64,
66) -> AuthResult<organization_members::Model> {
67 Ok(organization_members::ActiveModel {
68 organization_id: Set(organization_id),
69 user_id: Set(user_id),
70 role: Set(OrgMemberRole::Owner),
71 joined_at: Set(Utc::now()),
72 join_method: Set(OrgJoinMethod::Direct),
73 ..Default::default()
74 }
75 .insert(db)
76 .await?)
77}
78
79/// 会籍写入的统一入口:已有行就复活,没有就新建。
80/// `left_at` / `banned_at` 同时清空才算复活,两个字段各自记录一次"被赶出去"的原因,
81/// 直接覆盖会丢掉历史,所以只清标志位,历史留在事件与兑换记录里。
82pub(crate) async fn upsert_join<C: ConnectionTrait>(
83 db: &C,
84 organization_id: Uuid,
85 user_id: i64,
86 role: OrgMemberRole,
87 method: OrgJoinMethod,
88 invited_by: Option<i64>,
89 invite_id: Option<Uuid>,
90) -> AuthResult<(organization_members::Model, bool)> {
91 match find_any(db, organization_id, user_id).await? {
92 Some(existing) => {
93 if existing.banned_at.is_some() {
94 return Err(AuthError::OrganizationMemberBanned);
95 }
96 if existing.left_at.is_none() {
97 return Ok((existing, false));
98 }
99 let mut active: organization_members::ActiveModel = existing.into();
100 active.role = Set(role);
101 active.joined_at = Set(Utc::now());
102 active.left_at = Set(None);
103 active.ban_reason = Set(None);
104 active.invited_by = Set(invited_by);
105 active.join_method = Set(method);
106 active.invite_id = Set(invite_id);
107 active.version = Set(active.version.unwrap() + 1);
108 Ok((active.update(db).await?, true))
109 }
110 None => {
111 let row = organization_members::ActiveModel {
112 organization_id: Set(organization_id),
113 user_id: Set(user_id),
114 role: Set(role),
115 joined_at: Set(Utc::now()),
116 invited_by: Set(invited_by),
117 join_method: Set(method),
118 invite_id: Set(invite_id),
119 ..Default::default()
120 }
121 .insert(db)
122 .await?;
123 Ok((row, true))
124 }
125 }
126}
127
128impl AuthSvc {
129 // 分页与过滤开关直接对应查询参数,拆结构体没有收益。
130 #[allow(clippy::too_many_arguments)]
131 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
132 pub async fn list_members(
133 &self,
134 actor_user_id: i64,
135 organization_id: Uuid,
136 include_inactive: bool,
137 include_banned: bool,
138 limit: u64,
139 offset: u64,
140 request_id: Option<&str>,
141 ) -> AuthResult<Vec<MemberSummary>> {
142 self.require_permission(actor_user_id, organization_id, "member.view", request_id)
143 .await?;
144 let mut query = organization_members::Entity::find()
145 .filter(organization_members::Column::OrganizationId.eq(organization_id));
146 if !include_inactive {
147 query = query.filter(organization_members::Column::LeftAt.is_null());
148 }
149 if !include_banned {
150 query = query.filter(organization_members::Column::BannedAt.is_null());
151 }
152 let rows = query
153 .order_by_asc(organization_members::Column::JoinedAt)
154 .limit(limit)
155 .offset(offset)
156 .all(self.db().read())
157 .await?;
158 let mut out = Vec::with_capacity(rows.len());
159 for row in rows {
160 out.push(self.member_summary(&row).await?);
161 }
162 Ok(out)
163 }
164
165 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
166 pub async fn add_member(
167 &self,
168 actor_user_id: i64,
169 organization_id: Uuid,
170 body: AddMemberBody,
171 request_id: Option<&str>,
172 ) -> AuthResult<(MemberSummary, bool)> {
173 self.require_permission(actor_user_id, organization_id, "member.add", request_id)
174 .await?;
175 let role = body.role.unwrap_or(OrgMemberRole::Member);
176 if role == OrgMemberRole::Owner {
177 return Err(AuthError::Forbidden);
178 }
179 // 角色绑定属于 `role.assign` 范畴:只有 `member.add` 的人不能把新成员直接绑进高权角色。
180 if !body.role_keys.is_empty() {
181 self.require_permission(actor_user_id, organization_id, "role.assign", request_id)
182 .await?;
183 }
184 let role_keys = role::load_keys(organization_id, &body.role_keys, self.db().read()).await?;
185 users::Entity::find_by_id(body.user_id)
186 .filter(users::Column::DeletedAt.is_null())
187 .one(self.db().read())
188 .await?
189 .ok_or(AuthError::UserNotFound)?;
190
191 let txn = self.db().write().begin().await?;
192 let (row, created) = upsert_join(
193 &txn,
194 organization_id,
195 body.user_id,
196 role,
197 OrgJoinMethod::Direct,
198 Some(actor_user_id),
199 None,
200 )
201 .await?;
202 for role_id in &role_keys {
203 role::bind_role(&txn, row.id, *role_id, actor_user_id).await?;
204 }
205 if created && let Some(default_role) = self.default_role_id(organization_id).await? {
206 role::bind_role(&txn, row.id, default_role, actor_user_id).await?;
207 }
208 bump_version(&txn, organization_id).await?;
209 let refreshed = load_by_id(&txn, organization_id, row.id).await?;
210 self.emit_member(
211 &txn,
212 organization_id,
213 actor_user_id,
214 serde_json::json!({
215 "id": refreshed.id,
216 "user_id": refreshed.user_id,
217 "action": "add",
218 "created": created,
219 "request_id": request_id,
220 }),
221 request_id,
222 )
223 .await?;
224 txn.commit().await?;
225
226 self.invalidate_org(organization_id).await?;
227 // 返回的 version 必须是成员行自己的版本:组织版本对齐的是另一条乐观锁链。
228 let summary = self.member_summary(&refreshed).await?;
229 Ok((summary, created))
230 }
231
232 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
233 pub async fn remove_member(
234 &self,
235 actor_user_id: i64,
236 organization_id: Uuid,
237 member_id: Option<Uuid>,
238 user_id: i64,
239 reason: Option<&str>,
240 request_id: Option<&str>,
241 ) -> AuthResult<bool> {
242 self.require_permission(actor_user_id, organization_id, "member.remove", request_id)
243 .await?;
244 let reason = normalize_reason(reason)?;
245 let row = match member_id {
246 Some(id) => load_by_id(self.db().read(), organization_id, id).await?,
247 None => find_any(self.db().read(), organization_id, user_id)
248 .await?
249 .ok_or(AuthError::OrganizationMemberNotFound)?,
250 };
251 if row.role == OrgMemberRole::Owner {
252 return Err(AuthError::OrganizationOwnerRequired);
253 }
254 if row.left_at.is_some() {
255 return Ok(false);
256 }
257
258 let txn = self.db().write().begin().await?;
259 organization_members::Entity::update_many()
260 .col_expr(
261 organization_members::Column::LeftAt,
262 Expr::value(Utc::now()),
263 )
264 .col_expr(
265 organization_members::Column::BanReason,
266 Expr::value(reason.clone()),
267 )
268 .col_expr(
269 organization_members::Column::Version,
270 Expr::col(organization_members::Column::Version).add(1),
271 )
272 .filter(organization_members::Column::Id.eq(row.id))
273 .exec(&txn)
274 .await?;
275 // 离组即失去全部授予:不清掉绑定,复活时会带着旧角色回来,等于悄悄恢复权限。
276 role::clear_bindings(&txn, row.id).await?;
277 organization_member_permissions::Entity::delete_many()
278 .filter(organization_member_permissions::Column::MemberId.eq(row.id))
279 .exec(&txn)
280 .await?;
281 let version = bump_version(&txn, organization_id).await?;
282 self.emit_member(
283 &txn,
284 organization_id,
285 actor_user_id,
286 serde_json::json!({
287 "id": row.id,
288 "user_id": row.user_id,
289 "action": "remove",
290 "version": version,
291 "request_id": request_id,
292 }),
293 request_id,
294 )
295 .await?;
296 txn.commit().await?;
297
298 self.invalidate_org(organization_id).await?;
299 Ok(true)
300 }
301
302 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
303 pub async fn leave_organization(
304 &self,
305 actor_user_id: i64,
306 organization_id: Uuid,
307 request_id: Option<&str>,
308 ) -> AuthResult<bool> {
309 let row = find_any(self.db().read(), organization_id, actor_user_id)
310 .await?
311 .ok_or(AuthError::OrganizationMemberNotFound)?;
312 if row.role == OrgMemberRole::Owner {
313 return Err(AuthError::OrganizationOwnerRequired);
314 }
315 if row.left_at.is_some() {
316 return Ok(false);
317 }
318
319 let txn = self.db().write().begin().await?;
320 organization_members::Entity::update_many()
321 .col_expr(
322 organization_members::Column::LeftAt,
323 Expr::value(Utc::now()),
324 )
325 .col_expr(
326 organization_members::Column::Version,
327 Expr::col(organization_members::Column::Version).add(1),
328 )
329 .filter(organization_members::Column::Id.eq(row.id))
330 .exec(&txn)
331 .await?;
332 role::clear_bindings(&txn, row.id).await?;
333 organization_member_permissions::Entity::delete_many()
334 .filter(organization_member_permissions::Column::MemberId.eq(row.id))
335 .exec(&txn)
336 .await?;
337 let version = bump_version(&txn, organization_id).await?;
338 self.emit_member(
339 &txn,
340 organization_id,
341 actor_user_id,
342 serde_json::json!({
343 "id": row.id,
344 "user_id": row.user_id,
345 "action": "leave",
346 "version": version,
347 "request_id": request_id,
348 }),
349 request_id,
350 )
351 .await?;
352 txn.commit().await?;
353
354 self.invalidate_org(organization_id).await?;
355 Ok(true)
356 }
357
358 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, request_id = %request_id.unwrap_or_default()))]
359 pub async fn set_member_role(
360 &self,
361 actor_user_id: i64,
362 organization_id: Uuid,
363 member_id: Uuid,
364 body: SetMemberRoleBody,
365 request_id: Option<&str>,
366 ) -> AuthResult<MemberSummary> {
367 self.require_permission(
368 actor_user_id,
369 organization_id,
370 "member.role.update",
371 request_id,
372 )
373 .await?;
374 if body.role == OrgMemberRole::Owner {
375 return Err(AuthError::Forbidden);
376 }
377 let row = load_by_id(self.db().read(), organization_id, member_id).await?;
378 if row.role == OrgMemberRole::Owner {
379 return Err(AuthError::OrganizationOwnerRequired);
380 }
381 if body.expected_version > 0 && row.version != body.expected_version {
382 return Err(AuthError::VersionMismatch {
383 current: row.version,
384 });
385 }
386
387 let owner_role = role::find_by_key(organization_id, "owner", self.db().read()).await?;
388 let admin_role = role::find_by_key(organization_id, "admin", self.db().read()).await?;
389 let member_role = role::find_by_key(organization_id, "member", self.db().read()).await?;
390
391 let txn = self.db().write().begin().await?;
392 organization_members::Entity::update_many()
393 .col_expr(organization_members::Column::Role, Expr::value(body.role))
394 .col_expr(
395 organization_members::Column::Version,
396 Expr::col(organization_members::Column::Version).add(1),
397 )
398 .filter(organization_members::Column::Id.eq(member_id))
399 .exec(&txn)
400 .await?;
401 // 粗粒度角色与系统角色绑定必须同步,否则解析链上会出现"粗粒度是 ADMIN、
402 // 绑定里还留着 member"这种自相矛盾的组合。
403 let target = match body.role {
404 OrgMemberRole::Admin => admin_role.clone(),
405 _ => member_role.clone(),
406 };
407 for system in [owner_role, admin_role, member_role].into_iter().flatten() {
408 role::unbind_role(&txn, member_id, system.id).await?;
409 }
410 if let Some(system) = target {
411 role::bind_role(&txn, member_id, system.id, actor_user_id).await?;
412 }
413 let version = bump_version(&txn, organization_id).await?;
414 let refreshed = load_by_id(&txn, organization_id, member_id).await?;
415 self.emit_member(
416 &txn,
417 organization_id,
418 actor_user_id,
419 serde_json::json!({
420 "id": member_id,
421 "user_id": row.user_id,
422 "action": "set_role",
423 "role": refreshed.role,
424 "request_id": request_id,
425 }),
426 request_id,
427 )
428 .await?;
429 txn.commit().await?;
430
431 self.invalidate_org(organization_id).await?;
432 let mut summary = self.member_summary(&refreshed).await?;
433 summary.version = version;
434 Ok(summary)
435 }
436
437 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, request_id = %request_id.unwrap_or_default()))]
438 pub async fn ban_member(
439 &self,
440 actor_user_id: i64,
441 organization_id: Uuid,
442 member_id: Uuid,
443 reason: Option<&str>,
444 request_id: Option<&str>,
445 ) -> AuthResult<MemberSummary> {
446 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
447 .await?;
448 let reason = match reason.map(str::trim) {
449 None | Some("") => None,
450 Some(value) if value.len() > MAX_REASON_LEN => return Err(AuthError::MalformedRequest),
451 Some(value) => Some(value.to_owned()),
452 };
453 let row = load_by_id(self.db().read(), organization_id, member_id).await?;
454 if row.role == OrgMemberRole::Owner {
455 return Err(AuthError::OrganizationOwnerRequired);
456 }
457 if row.banned_at.is_some() {
458 return self.member_summary(&row).await;
459 }
460
461 let txn = self.db().write().begin().await?;
462 organization_members::Entity::update_many()
463 .col_expr(
464 organization_members::Column::BannedAt,
465 Expr::value(Utc::now()),
466 )
467 .col_expr(
468 organization_members::Column::BanReason,
469 Expr::value(reason.clone()),
470 )
471 .col_expr(
472 organization_members::Column::Version,
473 Expr::col(organization_members::Column::Version).add(1),
474 )
475 .filter(organization_members::Column::Id.eq(member_id))
476 .exec(&txn)
477 .await?;
478 // 封禁优先于一切授权,清空授予让"解封后权限自己回来"不可能发生。
479 role::clear_bindings(&txn, member_id).await?;
480 organization_member_permissions::Entity::delete_many()
481 .filter(organization_member_permissions::Column::MemberId.eq(member_id))
482 .exec(&txn)
483 .await?;
484 let version = bump_version(&txn, organization_id).await?;
485 let refreshed = load_by_id(&txn, organization_id, member_id).await?;
486 self.emit_member(
487 &txn,
488 organization_id,
489 actor_user_id,
490 serde_json::json!({
491 "id": member_id,
492 "user_id": row.user_id,
493 "action": "ban",
494 "reason": &reason,
495 "request_id": request_id,
496 }),
497 request_id,
498 )
499 .await?;
500 txn.commit().await?;
501
502 self.invalidate_org(organization_id).await?;
503 let mut summary = self.member_summary(&refreshed).await?;
504 summary.version = version;
505 Ok(summary)
506 }
507
508 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, request_id = %request_id.unwrap_or_default()))]
509 pub async fn unban_member(
510 &self,
511 actor_user_id: i64,
512 organization_id: Uuid,
513 member_id: Uuid,
514 request_id: Option<&str>,
515 ) -> AuthResult<MemberSummary> {
516 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
517 .await?;
518 let row = load_by_id(self.db().read(), organization_id, member_id).await?;
519 if row.banned_at.is_none() {
520 return self.member_summary(&row).await;
521 }
522
523 let txn = self.db().write().begin().await?;
524 // 解封必须把封禁的全部附带状态一起清掉,否则残留的到期时间与四个 allow_* 位会继续生效。
525 let unlocked = organization_members::Entity::update_many()
526 .col_expr(
527 organization_members::Column::BannedAt,
528 Expr::value(None::<chrono::DateTime<Utc>>),
529 )
530 .col_expr(
531 organization_members::Column::BanReason,
532 Expr::value(None::<String>),
533 )
534 .col_expr(
535 organization_members::Column::BanExpiresAt,
536 Expr::value(None::<chrono::DateTime<Utc>>),
537 )
538 .col_expr(
539 organization_members::Column::BanAllowAgentJoin,
540 Expr::value(false),
541 )
542 .col_expr(
543 organization_members::Column::BanAllowSpeak,
544 Expr::value(false),
545 )
546 .col_expr(
547 organization_members::Column::BanAllowPushRepo,
548 Expr::value(false),
549 )
550 .col_expr(
551 organization_members::Column::BanAllowCommentAny,
552 Expr::value(false),
553 )
554 .col_expr(
555 organization_members::Column::Version,
556 Expr::col(organization_members::Column::Version).add(1),
557 )
558 .filter(organization_members::Column::Id.eq(member_id))
559 .filter(organization_members::Column::BannedAt.is_not_null())
560 .exec(&txn)
561 .await?;
562 if unlocked.rows_affected != 1 {
563 txn.rollback().await?;
564 return Err(AuthError::VersionMismatch {
565 current: row.version,
566 });
567 }
568 bump_version(&txn, organization_id).await?;
569 let refreshed = load_by_id(&txn, organization_id, member_id).await?;
570 self.emit_member(
571 &txn,
572 organization_id,
573 actor_user_id,
574 serde_json::json!({
575 "id": member_id,
576 "user_id": row.user_id,
577 "action": "unban",
578 "request_id": request_id,
579 }),
580 request_id,
581 )
582 .await?;
583 txn.commit().await?;
584
585 self.invalidate_org(organization_id).await?;
586 let summary = self.member_summary(&refreshed).await?;
587 Ok(summary)
588 }
589
590 /// 按会籍 id 或 user_id 读会籍;两者都提供时以 member_id 为准。
591 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
592 pub async fn get_member(
593 &self,
594 actor_user_id: i64,
595 organization_id: Uuid,
596 member_id: Option<Uuid>,
597 user_id: i64,
598 request_id: Option<&str>,
599 ) -> AuthResult<MemberSummary> {
600 self.require_permission(actor_user_id, organization_id, "member.view", request_id)
601 .await?;
602 let row = match member_id {
603 Some(id) => load_by_id(self.db().read(), organization_id, id).await?,
604 None => find_any(self.db().read(), organization_id, user_id)
605 .await?
606 .ok_or(AuthError::OrganizationMemberNotFound)?,
607 };
608 self.member_summary(&row).await
609 }
610
611 /// 管理端更新会籍:别名、头像、通知偏好、免打扰。封禁中的会籍只走 `update_ban`。
612 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, request_id = %request_id.unwrap_or_default()))]
613 pub async fn update_member(
614 &self,
615 actor_user_id: i64,
616 organization_id: Uuid,
617 member_id: Uuid,
618 body: UpdateMemberBody,
619 request_id: Option<&str>,
620 ) -> AuthResult<MemberSummary> {
621 self.require_permission(
622 actor_user_id,
623 organization_id,
624 "member.role.update",
625 request_id,
626 )
627 .await?;
628 self.apply_member_update(
629 actor_user_id,
630 organization_id,
631 member_id,
632 body,
633 "update",
634 request_id,
635 )
636 .await
637 }
638
639 /// 成员自助更新:只允许改自己的别名、头像与通知偏好。
640 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
641 pub async fn update_self_member(
642 &self,
643 actor_user_id: i64,
644 organization_id: Uuid,
645 body: UpdateMemberBody,
646 request_id: Option<&str>,
647 ) -> AuthResult<MemberSummary> {
648 let row = find_active(self.db().read(), organization_id, actor_user_id)
649 .await?
650 .ok_or(AuthError::Forbidden)?;
651 self.apply_member_update(
652 actor_user_id,
653 organization_id,
654 row.id,
655 body,
656 "self_update",
657 request_id,
658 )
659 .await
660 }
661
662 async fn apply_member_update(
663 &self,
664 actor_user_id: i64,
665 organization_id: Uuid,
666 member_id: Uuid,
667 body: UpdateMemberBody,
668 action: &'static str,
669 request_id: Option<&str>,
670 ) -> AuthResult<MemberSummary> {
671 // 读取、版本校验与写回必须在同一事务内,否则并发封禁/离组/改角色会被旧快照覆盖。
672 let txn = self.db().write().begin().await?;
673 let row = load_by_id(&txn, organization_id, member_id).await?;
674 if body.expected_version > 0 && row.version != body.expected_version {
675 txn.rollback().await?;
676 return Err(AuthError::VersionMismatch {
677 current: row.version,
678 });
679 }
680 if action == "update" {
681 if row.role == OrgMemberRole::Owner {
682 txn.rollback().await?;
683 return Err(AuthError::OrganizationOwnerRequired);
684 }
685 if row.banned_at.is_some() {
686 txn.rollback().await?;
687 return Err(AuthError::Forbidden);
688 }
689 } else if row.banned_at.is_some() || row.left_at.is_some() {
690 txn.rollback().await?;
691 return Err(AuthError::Forbidden);
692 }
693 let alias_name = match body.alias_name {
694 None => row.alias_name.clone(),
695 Some(None) => None,
696 Some(Some(value)) => normalize_alias_field(value, MAX_ALIAS_NAME_LEN)?,
697 };
698 let alias_avatar_url = match body.alias_avatar_url {
699 None => row.alias_avatar_url.clone(),
700 Some(None) => None,
701 Some(Some(value)) => normalize_alias_field(value, MAX_ALIAS_AVATAR_URL_LEN)?,
702 };
703 let message_notifications = body
704 .message_notifications
705 .unwrap_or(row.message_notifications);
706 let muted = body.muted.unwrap_or(row.muted);
707 let mute_until = match body.mute_until {
708 None => row.mute_until,
709 Some(value) => value,
710 };
711 let mobile_push = body.mobile_push.unwrap_or(row.mobile_push);
712 let suppress_everyone = body.suppress_everyone.unwrap_or(row.suppress_everyone);
713 let suppress_roles = body.suppress_roles.unwrap_or(row.suppress_roles);
714 let hide_muted_channels = body.hide_muted_channels.unwrap_or(row.hide_muted_channels);
715 // 条件写回:只有版本未变时才生效,防止旧快照覆盖并发已提交的封禁/离组/角色变更。
716 let written = organization_members::Entity::update_many()
717 .col_expr(
718 organization_members::Column::AliasName,
719 Expr::value(alias_name.clone()),
720 )
721 .col_expr(
722 organization_members::Column::AliasAvatarUrl,
723 Expr::value(alias_avatar_url.clone()),
724 )
725 .col_expr(
726 organization_members::Column::MessageNotifications,
727 Expr::value(message_notifications),
728 )
729 .col_expr(organization_members::Column::Muted, Expr::value(muted))
730 .col_expr(
731 organization_members::Column::MuteUntil,
732 Expr::value(mute_until),
733 )
734 .col_expr(
735 organization_members::Column::MobilePush,
736 Expr::value(mobile_push),
737 )
738 .col_expr(
739 organization_members::Column::SuppressEveryone,
740 Expr::value(suppress_everyone),
741 )
742 .col_expr(
743 organization_members::Column::SuppressRoles,
744 Expr::value(suppress_roles),
745 )
746 .col_expr(
747 organization_members::Column::HideMutedChannels,
748 Expr::value(hide_muted_channels),
749 )
750 .col_expr(
751 organization_members::Column::Version,
752 Expr::value(row.version + 1),
753 )
754 .filter(organization_members::Column::Id.eq(row.id))
755 .filter(organization_members::Column::Version.eq(row.version))
756 .exec(&txn)
757 .await?;
758 if written.rows_affected != 1 {
759 txn.rollback().await?;
760 return Err(AuthError::VersionMismatch {
761 current: row.version,
762 });
763 }
764 let version = bump_version(&txn, organization_id).await?;
765 let refreshed = load_by_id(&txn, organization_id, row.id).await?;
766 self.emit_member(
767 &txn,
768 organization_id,
769 actor_user_id,
770 serde_json::json!({
771 "id": row.id,
772 "user_id": row.user_id,
773 "action": action,
774 "alias_name": alias_name,
775 "alias_avatar_url": alias_avatar_url,
776 "request_id": request_id,
777 }),
778 request_id,
779 )
780 .await?;
781 txn.commit().await?;
782
783 self.invalidate_org(organization_id).await?;
784 let mut summary = self.member_summary(&refreshed).await?;
785 summary.version = version;
786 Ok(summary)
787 }
788
789 /// 封禁列表:只看 `banned_at` 非空的会籍。
790 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
791 pub async fn list_bans(
792 &self,
793 actor_user_id: i64,
794 organization_id: Uuid,
795 limit: u64,
796 offset: u64,
797 request_id: Option<&str>,
798 ) -> AuthResult<Vec<MemberSummary>> {
799 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
800 .await?;
801 let rows = organization_members::Entity::find()
802 .filter(organization_members::Column::OrganizationId.eq(organization_id))
803 .filter(organization_members::Column::BannedAt.is_not_null())
804 .order_by_desc(organization_members::Column::BannedAt)
805 .limit(limit)
806 .offset(offset)
807 .all(self.db().read())
808 .await?;
809 let mut out = Vec::with_capacity(rows.len());
810 for row in rows {
811 out.push(self.member_summary(&row).await?);
812 }
813 Ok(out)
814 }
815
816 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
817 pub async fn get_ban(
818 &self,
819 actor_user_id: i64,
820 organization_id: Uuid,
821 user_id: i64,
822 request_id: Option<&str>,
823 ) -> AuthResult<MemberSummary> {
824 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
825 .await?;
826 let row = find_any(self.db().read(), organization_id, user_id)
827 .await?
828 .ok_or(AuthError::OrganizationMemberNotFound)?;
829 if row.banned_at.is_none() {
830 return Err(AuthError::OrganizationMemberNotFound);
831 }
832 self.member_summary(&row).await
833 }
834
835 /// 改封禁原因、到期时间与四个放行位;会籍必须处于封禁中。
836 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
837 pub async fn update_ban(
838 &self,
839 actor_user_id: i64,
840 organization_id: Uuid,
841 user_id: i64,
842 body: UpdateBanBody,
843 request_id: Option<&str>,
844 ) -> AuthResult<MemberSummary> {
845 self.require_permission(actor_user_id, organization_id, "member.ban", request_id)
846 .await?;
847 let row = find_any(self.db().read(), organization_id, user_id)
848 .await?
849 .ok_or(AuthError::OrganizationMemberNotFound)?;
850 if row.banned_at.is_none() {
851 return Err(AuthError::OrganizationMemberNotFound);
852 }
853 let reason = match body.reason {
854 None => row.ban_reason.clone(),
855 Some(value) => match value {
856 None => None,
857 Some(value) => {
858 let trimmed = value.trim();
859 if trimmed.len() > MAX_REASON_LEN {
860 return Err(AuthError::MalformedRequest);
861 }
862 Some(trimmed.to_owned()).filter(|value| !value.is_empty())
863 }
864 },
865 };
866 let expires_at = match body.expires_at {
867 None => row.ban_expires_at,
868 Some(value) => value,
869 };
870
871 let txn = self.db().write().begin().await?;
872 let mut active: organization_members::ActiveModel = row.clone().into();
873 active.ban_reason = Set(reason.clone());
874 active.ban_expires_at = Set(expires_at);
875 if let Some(value) = body.allow_agent_join {
876 active.ban_allow_agent_join = Set(value);
877 }
878 if let Some(value) = body.allow_speak {
879 active.ban_allow_speak = Set(value);
880 }
881 if let Some(value) = body.allow_push_repo {
882 active.ban_allow_push_repo = Set(value);
883 }
884 if let Some(value) = body.allow_comment_any {
885 active.ban_allow_comment_any = Set(value);
886 }
887 active.version = Set(row.version + 1);
888 active.update(&txn).await?;
889 let version = bump_version(&txn, organization_id).await?;
890 let refreshed = load_by_id(&txn, organization_id, row.id).await?;
891 self.emit_member(
892 &txn,
893 organization_id,
894 actor_user_id,
895 serde_json::json!({
896 "id": row.id,
897 "user_id": row.user_id,
898 "action": "update_ban",
899 "expires_at": expires_at,
900 "request_id": request_id,
901 }),
902 request_id,
903 )
904 .await?;
905 txn.commit().await?;
906
907 self.invalidate_org(organization_id).await?;
908 let mut summary = self.member_summary(&refreshed).await?;
909 summary.version = version;
910 Ok(summary)
911 }
912
913 /// 成员事件与业务写入同事务入队:提交时一起可见,回滚时一起消失。
914 pub(crate) async fn emit_member(
915 &self,
916 txn: &sea_orm::DatabaseTransaction,
917 organization_id: Uuid,
918 actor_user_id: i64,
919 payload: serde_json::Value,
920 _request_id: Option<&str>,
921 ) -> AuthResult<()> {
922 let labels = crate::events::Labels::new()
923 .with(label::ORGANIZATION_ID, organization_id)
924 .with(label::ACTOR_USER_ID, actor_user_id);
925 self.emit_tx(
926 txn,
927 label::ORGANIZATION_MEMBER_UPDATE,
928 labels,
929 payload,
930 None,
931 )
932 .await
933 }
934}
935
936/// 别名/别名头像的规范化:空白视为清空,超长按 400 处理。
937fn normalize_alias_field(value: String, max: usize) -> AuthResult<Option<String>> {
938 normalize_alias(&value, "alias", max).map_err(|_| AuthError::MalformedRequest)
939}
Added lib/auth/src/organization/mod.rs lib/auth/src/organization/mod.rs +10 −0 View lib/auth/src/organization/mod.rs
@@ -0,0 +1,10 @@
1pub mod error;
2pub mod invite;
3pub mod join;
4pub mod member;
5pub mod permission;
6pub mod role;
7pub mod service;
8pub mod types;
9
10pub use types::*;
Added lib/auth/src/organization/permission.rs lib/auth/src/organization/permission.rs +324 −0 View lib/auth/src/organization/permission.rs
@@ -0,0 +1,324 @@
1use std::time::Duration;
2
3use sea_orm::{ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QuerySelect};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::AuthSvc;
8use crate::errors::{AuthError, AuthResult};
9use crate::models::space::{
10 OrgMemberRole, PermissionEffect, organization_member_permissions, organization_member_roles,
11 organization_members, organization_role_permissions,
12};
13use crate::organization::types::{
14 PERMISSION_CACHE_TTL_SECS, PERMISSIONS, PermissionCheck, Resolution, SYSTEM_ROLE_ADMIN,
15 SYSTEM_ROLE_OWNER, is_admin_permission, is_known_permission,
16};
17use crate::organization::{member, role, service};
18
19/// 缓存进 Redis 的判定核心。版本号相同的组织共享同一份核心,
20/// 任何写操作推进 `organizations.version` 都会让整份核心失效。
21#[derive(Clone, Debug, Default, Serialize, Deserialize)]
22pub(crate) struct Kernel {
23 pub version: i32,
24 pub is_owner: bool,
25 pub is_admin: bool,
26 pub role_keys: Vec<String>,
27 pub role_allow: Vec<String>,
28 pub role_deny: Vec<String>,
29 pub direct_allow: Vec<String>,
30 pub direct_deny: Vec<String>,
31}
32
33fn push_unique(target: &mut Vec<String>, value: &str) {
34 if !target.iter().any(|existing| existing == value) {
35 target.push(value.to_owned());
36 }
37}
38
39pub(crate) async fn load_kernel<C: ConnectionTrait>(
40 db: &C,
41 version: i32,
42 membership: &organization_members::Model,
43) -> AuthResult<Kernel> {
44 let role_ids = organization_member_roles::Entity::find()
45 .select_only()
46 .column(organization_member_roles::Column::RoleId)
47 .filter(organization_member_roles::Column::MemberId.eq(membership.id))
48 .into_tuple::<Uuid>()
49 .all(db)
50 .await?;
51 let role_keys = role::keys_of_ids(db, &role_ids).await?;
52 let is_owner = membership.role == OrgMemberRole::Owner
53 || role_keys.iter().any(|key| key == SYSTEM_ROLE_OWNER);
54 let is_admin = is_owner
55 || membership.role == OrgMemberRole::Admin
56 || role_keys.iter().any(|key| key == SYSTEM_ROLE_ADMIN);
57
58 let mut kernel = Kernel {
59 version,
60 is_owner,
61 is_admin,
62 role_keys,
63 ..Default::default()
64 };
65
66 if !role_ids.is_empty() {
67 let grants = organization_role_permissions::Entity::find()
68 .filter(organization_role_permissions::Column::RoleId.is_in(role_ids))
69 .all(db)
70 .await?;
71 for grant in grants {
72 if !is_known_permission(&grant.permission) {
73 continue;
74 }
75 match grant.effect {
76 PermissionEffect::Deny => push_unique(&mut kernel.role_deny, &grant.permission),
77 PermissionEffect::Allow => push_unique(&mut kernel.role_allow, &grant.permission),
78 }
79 }
80 }
81
82 let direct = organization_member_permissions::Entity::find()
83 .filter(organization_member_permissions::Column::MemberId.eq(membership.id))
84 .all(db)
85 .await?;
86 for grant in direct {
87 if !is_known_permission(&grant.permission) {
88 continue;
89 }
90 match grant.effect {
91 PermissionEffect::Deny => push_unique(&mut kernel.direct_deny, &grant.permission),
92 PermissionEffect::Allow => push_unique(&mut kernel.direct_allow, &grant.permission),
93 }
94 }
95
96 Ok(kernel)
97}
98
99fn decide(kernel: &Kernel, permission: &str) -> (&'static str, &'static str) {
100 if !is_known_permission(permission) {
101 return ("unknown_permission", "default_deny");
102 }
103 if kernel.is_owner {
104 return ("", "owner");
105 }
106 if kernel.is_admin && is_admin_permission(permission) {
107 return ("", "admin");
108 }
109 // 直挂层优先于角色层,层内 DENY 优先。两层都显式覆盖,
110 // 这样"角色给了、成员被单独收回"和"成员单独补授"都能表达。
111 if kernel.direct_deny.iter().any(|key| key == permission) {
112 return ("direct_deny", "direct");
113 }
114 if kernel.direct_allow.iter().any(|key| key == permission) {
115 return ("", "direct");
116 }
117 if kernel.role_deny.iter().any(|key| key == permission) {
118 return ("role_deny", "role");
119 }
120 if kernel.role_allow.iter().any(|key| key == permission) {
121 return ("", "role");
122 }
123 ("no_grant", "default_deny")
124}
125
126fn allowed_list(kernel: &Kernel) -> Vec<String> {
127 PERMISSIONS
128 .iter()
129 .filter(|permission| decide(kernel, permission).0.is_empty())
130 .map(|permission| (*permission).to_owned())
131 .collect()
132}
133
134async fn cached_kernel(
135 svc: &AuthSvc,
136 organization_id: Uuid,
137 user_id: i64,
138 version: i32,
139 membership: &organization_members::Model,
140) -> AuthResult<Kernel> {
141 let key = service::perm_cache_prefix(organization_id, user_id);
142 if let Ok(Some(bytes)) = svc.cache().get_bytes(&key).await
143 && let Ok(kernel) = serde_json::from_slice::<Kernel>(&bytes)
144 && kernel.version == version
145 {
146 return Ok(kernel);
147 }
148 let kernel = load_kernel(svc.db().read(), version, membership).await?;
149 if let Ok(bytes) = serde_json::to_vec(&kernel)
150 && let Err(error) = svc
151 .cache()
152 .set_ex(&key, bytes, Duration::from_secs(PERMISSION_CACHE_TTL_SECS))
153 .await
154 {
155 tracing::warn!(%error, %organization_id, user_id, "failed to cache the permission kernel");
156 }
157 Ok(kernel)
158}
159
160/// 前置闸门:组织状态 + 活跃会籍 + 判定核心。批量校验只走一次。
161enum Gate {
162 Denied {
163 version: i32,
164 reason: &'static str,
165 matched_by: &'static str,
166 },
167 Ready {
168 version: i32,
169 kernel: Kernel,
170 },
171}
172
173impl Gate {
174 fn version(&self) -> i32 {
175 match self {
176 Self::Denied { version, .. } | Self::Ready { version, .. } => *version,
177 }
178 }
179
180 fn resolution(&self, permission: &str) -> Resolution {
181 match self {
182 Self::Denied {
183 reason, matched_by, ..
184 } => Resolution {
185 allowed: false,
186 reason,
187 matched_by,
188 effective: Vec::new(),
189 },
190 Self::Ready { kernel, .. } => {
191 let (reason, matched_by) = decide(kernel, permission);
192 Resolution {
193 allowed: reason.is_empty(),
194 reason,
195 matched_by,
196 effective: allowed_list(kernel),
197 }
198 }
199 }
200 }
201}
202
203async fn gate(
204 svc: &AuthSvc,
205 organization_id: Uuid,
206 user_id: i64,
207 _request_id: Option<&str>,
208) -> AuthResult<Gate> {
209 let organization = svc.load_organization(organization_id).await?;
210 let version = organization.version;
211 if organization.status != crate::models::space::OrganizationStatus::Active {
212 return Ok(Gate::Denied {
213 version,
214 reason: "org_inactive",
215 matched_by: "org_status",
216 });
217 }
218 let membership = match member::find_any(svc.db().read(), organization_id, user_id).await? {
219 Some(row) => row,
220 None => {
221 return Ok(Gate::Denied {
222 version,
223 reason: "not_member",
224 matched_by: "membership",
225 });
226 }
227 };
228 if membership.banned_at.is_some() {
229 return Ok(Gate::Denied {
230 version,
231 reason: "member_banned",
232 matched_by: "membership",
233 });
234 }
235 if membership.left_at.is_some() {
236 return Ok(Gate::Denied {
237 version,
238 reason: "member_left",
239 matched_by: "membership",
240 });
241 }
242
243 let kernel = cached_kernel(svc, organization_id, user_id, version, &membership).await?;
244 Ok(Gate::Ready { version, kernel })
245}
246
247/// 唯一判定入口。顺序固定:组织状态 → 活跃会籍 → Owner → Admin 固有集
248/// → 角色 Allow/Deny → 成员直挂 Allow/Deny。
249#[tracing::instrument(skip_all, fields(organization_id = %organization_id, user_id = user_id, permission = %permission, request_id = %request_id.unwrap_or_default()))]
250pub(crate) async fn resolve(
251 svc: &AuthSvc,
252 organization_id: Uuid,
253 user_id: i64,
254 permission: &str,
255 request_id: Option<&str>,
256) -> AuthResult<Resolution> {
257 let gate = gate(svc, organization_id, user_id, request_id).await?;
258 Ok(gate.resolution(permission))
259}
260
261impl AuthSvc {
262 /// 平台管理员越过会籍判定,但仍受组织状态约束。
263 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
264 pub async fn check_permission(
265 &self,
266 actor_user_id: i64,
267 organization_id: Uuid,
268 user_id: i64,
269 permission: &str,
270 request_id: Option<&str>,
271 ) -> AuthResult<PermissionCheck> {
272 let target = if user_id == 0 { actor_user_id } else { user_id };
273 if target != actor_user_id && !self.is_platform_admin(actor_user_id).await? {
274 return Err(AuthError::Forbidden);
275 }
276 let gate = gate(self, organization_id, target, request_id).await?;
277 let resolution = gate.resolution(permission.trim());
278 Ok(PermissionCheck {
279 allowed: resolution.allowed,
280 reason: resolution.reason.to_owned(),
281 matched_by: resolution.matched_by.to_owned(),
282 organization_version: gate.version(),
283 })
284 }
285
286 /// 批量校验:组织与会籍只加载一次,权限逐个走同一条判定链。
287 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
288 pub async fn check_permissions(
289 &self,
290 actor_user_id: i64,
291 organization_id: Uuid,
292 user_id: i64,
293 permissions: &[String],
294 request_id: Option<&str>,
295 ) -> AuthResult<Vec<PermissionCheck>> {
296 let target = if user_id == 0 { actor_user_id } else { user_id };
297 if target != actor_user_id && !self.is_platform_admin(actor_user_id).await? {
298 return Err(AuthError::Forbidden);
299 }
300 let gate = gate(self, organization_id, target, request_id).await?;
301 let version = gate.version();
302 Ok(permissions
303 .iter()
304 .map(|permission| {
305 let resolution = gate.resolution(permission.trim());
306 PermissionCheck {
307 allowed: resolution.allowed,
308 reason: resolution.reason.to_owned(),
309 matched_by: resolution.matched_by.to_owned(),
310 organization_version: version,
311 }
312 })
313 .collect())
314 }
315}
316
317/// 列表展示用的有效权限集,复用同一条解析链。
318pub(crate) async fn effective<C: ConnectionTrait>(
319 db: &C,
320 membership: &organization_members::Model,
321) -> AuthResult<Vec<String>> {
322 let kernel = load_kernel(db, 0, membership).await?;
323 Ok(allowed_list(&kernel))
324}
Added lib/auth/src/organization/role.rs lib/auth/src/organization/role.rs +857 −0 View lib/auth/src/organization/role.rs
@@ -0,0 +1,857 @@
1use sea_orm::entity::Set;
2use sea_orm::sea_query::{Expr, ExprTrait};
3use sea_orm::{
4 ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, PaginatorTrait, QueryFilter,
5 QueryOrder, QuerySelect, TransactionTrait,
6};
7use uuid::Uuid;
8
9use crate::AuthSvc;
10use crate::errors::{AuthError, AuthResult};
11use crate::events::label;
12use crate::models::space::{
13 PermissionEffect, organization_member_roles, organization_role_permissions, organization_roles,
14};
15use crate::organization::service::{bump_version, normalize_role_key, normalize_role_name};
16use crate::organization::types::{
17 CreateRoleBody, MemberSummary, PermissionGrant, PermissionGrant as Grant, RoleSummary,
18 SYSTEM_ROLE_ADMIN, SYSTEM_ROLE_MEMBER, SYSTEM_ROLE_OWNER, SetRolePermissionsBody,
19 is_known_permission,
20};
21use crate::organization::{error, member};
22
23/// 内置角色的固有授权。OWNER 不需要显式授权(解析链直接短路),
24/// ADMIN 的固有集写在 permissions.rs 的白名单里,这里只落 MEMBER 的默认值。
25const MEMBER_DEFAULTS: &[&str] = &[
26 "org.view",
27 "member.view",
28 "role.view",
29 "space.view_space",
30 "space.read",
31 "space.message",
32];
33
34pub(crate) async fn seed_system_roles<C: ConnectionTrait>(
35 db: &C,
36 organization_id: Uuid,
37) -> AuthResult<Uuid> {
38 let owner = organization_roles::ActiveModel {
39 organization_id: Set(organization_id),
40 key: Set(SYSTEM_ROLE_OWNER.to_owned()),
41 name: Set("Owner".to_owned()),
42 description: Set(Some("full control of the organization".to_owned())),
43 rank: Set(100),
44 system: Set(true),
45 default_role: Set(false),
46 ..Default::default()
47 }
48 .insert(db)
49 .await?;
50 let admin = organization_roles::ActiveModel {
51 organization_id: Set(organization_id),
52 key: Set(SYSTEM_ROLE_ADMIN.to_owned()),
53 name: Set("Admin".to_owned()),
54 description: Set(Some("manages members, roles and invites".to_owned())),
55 rank: Set(50),
56 system: Set(true),
57 default_role: Set(false),
58 ..Default::default()
59 }
60 .insert(db)
61 .await?;
62 let member = organization_roles::ActiveModel {
63 organization_id: Set(organization_id),
64 key: Set(SYSTEM_ROLE_MEMBER.to_owned()),
65 name: Set("Member".to_owned()),
66 description: Set(Some("default role for every new member".to_owned())),
67 rank: Set(0),
68 system: Set(true),
69 default_role: Set(true),
70 ..Default::default()
71 }
72 .insert(db)
73 .await?;
74
75 for permission in MEMBER_DEFAULTS {
76 organization_role_permissions::ActiveModel {
77 role_id: Set(member.id),
78 permission: Set((*permission).to_owned()),
79 effect: Set(PermissionEffect::Allow),
80 ..Default::default()
81 }
82 .insert(db)
83 .await?;
84 }
85 let _ = admin;
86 Ok(owner.id)
87}
88
89pub(crate) async fn find_by_key<C: ConnectionTrait>(
90 organization_id: Uuid,
91 key: &str,
92 db: &C,
93) -> AuthResult<Option<organization_roles::Model>> {
94 Ok(organization_roles::Entity::find()
95 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
96 .filter(organization_roles::Column::Key.eq(key))
97 .one(db)
98 .await?)
99}
100
101pub(crate) async fn keys_of_ids<C: ConnectionTrait>(
102 db: &C,
103 role_ids: &[Uuid],
104) -> AuthResult<Vec<String>> {
105 if role_ids.is_empty() {
106 return Ok(Vec::new());
107 }
108 Ok(organization_roles::Entity::find()
109 .select_only()
110 .column(organization_roles::Column::Key)
111 .filter(organization_roles::Column::Id.is_in(role_ids.to_vec()))
112 .order_by_asc(organization_roles::Column::Rank)
113 .into_tuple::<String>()
114 .all(db)
115 .await?)
116}
117
118pub(crate) async fn role_keys_of<C: ConnectionTrait>(
119 db: &C,
120 member_id: Uuid,
121) -> AuthResult<Vec<String>> {
122 let ids = organization_member_roles::Entity::find()
123 .select_only()
124 .column(organization_member_roles::Column::RoleId)
125 .filter(organization_member_roles::Column::MemberId.eq(member_id))
126 .into_tuple::<Uuid>()
127 .all(db)
128 .await?;
129 keys_of_ids(db, &ids).await
130}
131
132pub(crate) async fn grants_of<C: ConnectionTrait>(
133 db: &C,
134 role_id: Uuid,
135) -> AuthResult<Vec<PermissionGrant>> {
136 let rows = organization_role_permissions::Entity::find()
137 .filter(organization_role_permissions::Column::RoleId.eq(role_id))
138 .order_by_asc(organization_role_permissions::Column::Permission)
139 .all(db)
140 .await?;
141 Ok(rows
142 .into_iter()
143 .map(|row| Grant {
144 permission: row.permission,
145 effect: row.effect,
146 })
147 .collect())
148}
149
150/// 绑定幂等:唯一键冲突直接吞掉,重复授予不该报错。
151pub(crate) async fn bind_role<C: ConnectionTrait>(
152 db: &C,
153 member_id: Uuid,
154 role_id: Uuid,
155 granted_by: i64,
156) -> AuthResult<()> {
157 let existing = organization_member_roles::Entity::find()
158 .filter(organization_member_roles::Column::MemberId.eq(member_id))
159 .filter(organization_member_roles::Column::RoleId.eq(role_id))
160 .one(db)
161 .await?;
162 if existing.is_some() {
163 return Ok(());
164 }
165 organization_member_roles::ActiveModel {
166 member_id: Set(member_id),
167 role_id: Set(role_id),
168 granted_by: Set(Some(granted_by)),
169 ..Default::default()
170 }
171 .insert(db)
172 .await?;
173 Ok(())
174}
175
176pub(crate) async fn unbind_role<C: ConnectionTrait>(
177 db: &C,
178 member_id: Uuid,
179 role_id: Uuid,
180) -> AuthResult<u64> {
181 let result = organization_member_roles::Entity::delete_many()
182 .filter(organization_member_roles::Column::MemberId.eq(member_id))
183 .filter(organization_member_roles::Column::RoleId.eq(role_id))
184 .exec(db)
185 .await?;
186 Ok(result.rows_affected)
187}
188
189pub(crate) async fn clear_bindings<C: ConnectionTrait>(db: &C, member_id: Uuid) -> AuthResult<()> {
190 organization_member_roles::Entity::delete_many()
191 .filter(organization_member_roles::Column::MemberId.eq(member_id))
192 .exec(db)
193 .await?;
194 Ok(())
195}
196
197pub(crate) async fn load_keys<C: ConnectionTrait>(
198 organization_id: Uuid,
199 keys: &[String],
200 db: &C,
201) -> AuthResult<Vec<Uuid>> {
202 if keys.is_empty() {
203 return Ok(Vec::new());
204 }
205 let mut out = Vec::with_capacity(keys.len());
206 for key in keys {
207 let row = find_by_key(organization_id, key.trim(), db)
208 .await?
209 .ok_or(AuthError::OrganizationRoleNotFound)?;
210 if row.key == SYSTEM_ROLE_OWNER {
211 return Err(AuthError::Forbidden);
212 }
213 out.push(row.id);
214 }
215 Ok(out)
216}
217
218async fn replace_grants<C: ConnectionTrait>(
219 db: &C,
220 role_id: Uuid,
221 org_version_holder: &Uuid,
222 grants: &[PermissionGrant],
223) -> AuthResult<()> {
224 organization_role_permissions::Entity::delete_many()
225 .filter(organization_role_permissions::Column::RoleId.eq(role_id))
226 .exec(db)
227 .await?;
228 let _ = org_version_holder;
229 for grant in grants {
230 organization_role_permissions::ActiveModel {
231 role_id: Set(role_id),
232 permission: Set(grant.permission.clone()),
233 effect: Set(grant.effect),
234 ..Default::default()
235 }
236 .insert(db)
237 .await?;
238 }
239 Ok(())
240}
241
242impl AuthSvc {
243 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
244 pub async fn list_roles(
245 &self,
246 actor_user_id: i64,
247 organization_id: Uuid,
248 request_id: Option<&str>,
249 ) -> AuthResult<Vec<RoleSummary>> {
250 self.require_permission(actor_user_id, organization_id, "role.view", request_id)
251 .await?;
252 let rows = organization_roles::Entity::find()
253 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
254 .order_by_desc(organization_roles::Column::Rank)
255 .order_by_asc(organization_roles::Column::Key)
256 .all(self.db().read())
257 .await?;
258 let mut out = Vec::with_capacity(rows.len());
259 for row in rows {
260 out.push(self.role_summary(&row).await?);
261 }
262 Ok(out)
263 }
264
265 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
266 pub async fn create_role(
267 &self,
268 actor_user_id: i64,
269 organization_id: Uuid,
270 body: CreateRoleBody,
271 request_id: Option<&str>,
272 ) -> AuthResult<RoleSummary> {
273 self.require_permission(actor_user_id, organization_id, "role.create", request_id)
274 .await?;
275 let key = normalize_role_key(&body.key)?;
276 let name = normalize_role_name(&body.name)?;
277 let grants = Self::validate_grants(&body.permissions)?;
278 self.require_grantable(actor_user_id, organization_id, &grants, request_id)
279 .await?;
280 if find_by_key(organization_id, &key, self.db().read())
281 .await?
282 .is_some()
283 {
284 return Err(AuthError::OrganizationRoleKeyTaken);
285 }
286
287 let txn = self.db().write().begin().await?;
288 let role = organization_roles::ActiveModel {
289 organization_id: Set(organization_id),
290 key: Set(key.clone()),
291 name: Set(name.clone()),
292 description: Set(body.description.clone()),
293 rank: Set(body.rank),
294 system: Set(false),
295 default_role: Set(body.default_role),
296 self_assignable: Set(body.self_assignable),
297 ..Default::default()
298 }
299 .insert(&txn)
300 .await
301 .map_err(error::map_insert_conflict)?;
302 replace_grants(&txn, role.id, &organization_id, &grants).await?;
303 let version = bump_version(&txn, organization_id).await?;
304 let refreshed = load_role(&txn, organization_id, role.id).await?;
305 self.emit_role(
306 &txn,
307 organization_id,
308 actor_user_id,
309 serde_json::json!({
310 "id": role.id,
311 "key": &key,
312 "action": "create",
313 "request_id": request_id,
314 }),
315 request_id,
316 )
317 .await?;
318 txn.commit().await?;
319
320 self.invalidate_org(organization_id).await?;
321 let mut summary = self.role_summary(&refreshed).await?;
322 summary.version = version;
323 Ok(summary)
324 }
325
326 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
327 pub async fn set_role_permissions(
328 &self,
329 actor_user_id: i64,
330 organization_id: Uuid,
331 role_id: Uuid,
332 body: SetRolePermissionsBody,
333 request_id: Option<&str>,
334 ) -> AuthResult<RoleSummary> {
335 self.require_permission(
336 actor_user_id,
337 organization_id,
338 "role.permission.update",
339 request_id,
340 )
341 .await?;
342 let role = load_role(self.db().read(), organization_id, role_id).await?;
343 if role.key == SYSTEM_ROLE_OWNER {
344 return Err(AuthError::OrganizationRoleSystem);
345 }
346 let grants = Self::validate_grants(&body.permissions)?;
347 self.require_grantable(actor_user_id, organization_id, &grants, request_id)
348 .await?;
349
350 let txn = self.db().write().begin().await?;
351 replace_grants(&txn, role_id, &organization_id, &grants).await?;
352 organization_roles::Entity::update_many()
353 .col_expr(
354 organization_roles::Column::Version,
355 Expr::col(organization_roles::Column::Version).add(1),
356 )
357 .col_expr(
358 organization_roles::Column::UpdatedAt,
359 Expr::value(chrono::Utc::now()),
360 )
361 .filter(organization_roles::Column::Id.eq(role_id))
362 .exec(&txn)
363 .await?;
364 let version = bump_version(&txn, organization_id).await?;
365 let refreshed = load_role(&txn, organization_id, role_id).await?;
366 self.emit_role(
367 &txn,
368 organization_id,
369 actor_user_id,
370 serde_json::json!({
371 "id": role_id,
372 "key": &refreshed.key,
373 "action": "set_permissions",
374 "permissions": body.permissions.iter().map(|g| &g.permission).collect::<Vec<_>>(),
375 "request_id": request_id,
376 }),
377 request_id,
378 )
379 .await?;
380 txn.commit().await?;
381
382 self.invalidate_org(organization_id).await?;
383 let mut summary = self.role_summary(&refreshed).await?;
384 summary.version = version;
385 Ok(summary)
386 }
387
388 #[allow(clippy::too_many_arguments)]
389 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
390 pub async fn update_role(
391 &self,
392 actor_user_id: i64,
393 organization_id: Uuid,
394 role_id: Uuid,
395 name: Option<String>,
396 description: Option<String>,
397 rank: Option<i32>,
398 default_role: Option<bool>,
399 self_assignable: Option<bool>,
400 expected_version: i32,
401 request_id: Option<&str>,
402 ) -> AuthResult<RoleSummary> {
403 self.require_permission(actor_user_id, organization_id, "role.update", request_id)
404 .await?;
405 let role = load_role(self.db().read(), organization_id, role_id).await?;
406 let name = name.as_deref().map(normalize_role_name).transpose()?;
407 if expected_version > 0 && role.version != expected_version {
408 return Err(AuthError::VersionMismatch {
409 current: role.version,
410 });
411 }
412 if role.key == SYSTEM_ROLE_OWNER
413 && (rank.is_some() || default_role.is_some() || self_assignable.is_some())
414 {
415 return Err(AuthError::OrganizationRoleSystem);
416 }
417 // 系统角色的排名、默认身份、自助加入都是结构属性,任何改法都会改变权限基线。
418 if role.system && (rank.is_some() || default_role.is_some() || self_assignable.is_some()) {
419 return Err(AuthError::OrganizationRoleSystem);
420 }
421
422 let txn = self.db().write().begin().await?;
423 if let Some(true) = default_role {
424 // 默认角色唯一:换默认之前先清掉旧的,否则新成员会被绑两个默认角色。
425 organization_roles::Entity::update_many()
426 .col_expr(organization_roles::Column::DefaultRole, Expr::value(false))
427 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
428 .exec(&txn)
429 .await?;
430 }
431 let mut active: organization_roles::ActiveModel = role.clone().into();
432 if let Some(value) = name {
433 active.name = Set(value);
434 }
435 if let Some(value) = description {
436 active.description = Set(if value.trim().is_empty() {
437 None
438 } else {
439 Some(value)
440 });
441 }
442 if let Some(value) = rank {
443 active.rank = Set(value);
444 }
445 if let Some(value) = default_role {
446 active.default_role = Set(value);
447 }
448 if let Some(value) = self_assignable {
449 if role.system {
450 return Err(AuthError::OrganizationRoleSystem);
451 }
452 active.self_assignable = Set(value);
453 }
454 active.update(&txn).await?;
455 let version = bump_version(&txn, organization_id).await?;
456 let refreshed = load_role(&txn, organization_id, role_id).await?;
457 self.emit_role(
458 &txn,
459 organization_id,
460 actor_user_id,
461 serde_json::json!({
462 "id": role_id,
463 "key": &role.key,
464 "action": "update",
465 "request_id": request_id,
466 }),
467 request_id,
468 )
469 .await?;
470 txn.commit().await?;
471
472 self.invalidate_org(organization_id).await?;
473 let mut summary = self.role_summary(&refreshed).await?;
474 summary.version = version;
475 Ok(summary)
476 }
477
478 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
479 pub async fn delete_role(
480 &self,
481 actor_user_id: i64,
482 organization_id: Uuid,
483 role_id: Uuid,
484 request_id: Option<&str>,
485 ) -> AuthResult<bool> {
486 self.require_permission(actor_user_id, organization_id, "role.delete", request_id)
487 .await?;
488 let role = load_role(self.db().read(), organization_id, role_id).await?;
489 if role.system {
490 return Err(AuthError::OrganizationRoleSystem);
491 }
492 let bound = organization_member_roles::Entity::find()
493 .filter(organization_member_roles::Column::RoleId.eq(role_id))
494 .count(self.db().read())
495 .await?;
496 if bound > 0 {
497 return Err(AuthError::OrganizationRoleInUse);
498 }
499
500 let txn = self.db().write().begin().await?;
501 organization_role_permissions::Entity::delete_many()
502 .filter(organization_role_permissions::Column::RoleId.eq(role_id))
503 .exec(&txn)
504 .await?;
505 organization_roles::Entity::delete_many()
506 .filter(organization_roles::Column::Id.eq(role_id))
507 .exec(&txn)
508 .await?;
509 let version = bump_version(&txn, organization_id).await?;
510 self.emit_role(
511 &txn,
512 organization_id,
513 actor_user_id,
514 serde_json::json!({
515 "id": role_id,
516 "key": &role.key,
517 "action": "delete",
518 "version": version,
519 "request_id": request_id,
520 }),
521 request_id,
522 )
523 .await?;
524 txn.commit().await?;
525
526 self.invalidate_org(organization_id).await?;
527 Ok(true)
528 }
529
530 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, role_key = %role_key, request_id = %request_id.unwrap_or_default()))]
531 pub async fn assign_role(
532 &self,
533 actor_user_id: i64,
534 organization_id: Uuid,
535 member_id: Uuid,
536 role_key: &str,
537 request_id: Option<&str>,
538 ) -> AuthResult<Vec<String>> {
539 let member =
540 crate::organization::member::load_by_id(self.db().read(), organization_id, member_id)
541 .await?;
542 if member.banned_at.is_some() || member.left_at.is_some() {
543 return Err(AuthError::OrganizationMemberNotFound);
544 }
545 let role = find_by_key(organization_id, role_key.trim(), self.db().read())
546 .await?
547 .ok_or(AuthError::OrganizationRoleNotFound)?;
548 if role.key == SYSTEM_ROLE_OWNER {
549 return Err(AuthError::Forbidden);
550 }
551 if actor_user_id == member.user_id {
552 if role.system || !role.self_assignable {
553 return Err(AuthError::Forbidden);
554 }
555 } else {
556 self.require_permission(actor_user_id, organization_id, "role.assign", request_id)
557 .await?;
558 // 系统角色等同于整套权限包:代绑时必须具备其中的每一条权限。
559 if role.system {
560 let grants = grants_of(self.db().read(), role.id).await?;
561 self.require_grantable(actor_user_id, organization_id, &grants, request_id)
562 .await?;
563 }
564 }
565
566 let txn = self.db().write().begin().await?;
567 bind_role(&txn, member_id, role.id, actor_user_id).await?;
568 let version = bump_version(&txn, organization_id).await?;
569 let keys = role_keys_of(&txn, member_id).await?;
570 self.emit_role(
571 &txn,
572 organization_id,
573 actor_user_id,
574 serde_json::json!({
575 "member_id": member_id,
576 "role_key": &role.key,
577 "action": "assign",
578 "version": version,
579 "request_id": request_id,
580 }),
581 request_id,
582 )
583 .await?;
584 txn.commit().await?;
585
586 self.invalidate_org(organization_id).await?;
587 Ok(keys)
588 }
589
590 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, role_key = %role_key, request_id = %request_id.unwrap_or_default()))]
591 pub async fn unassign_role(
592 &self,
593 actor_user_id: i64,
594 organization_id: Uuid,
595 member_id: Uuid,
596 role_key: &str,
597 request_id: Option<&str>,
598 ) -> AuthResult<Vec<String>> {
599 let role = find_by_key(organization_id, role_key.trim(), self.db().read())
600 .await?
601 .ok_or(AuthError::OrganizationRoleNotFound)?;
602 if role.key == SYSTEM_ROLE_OWNER {
603 return Err(AuthError::OrganizationRoleSystem);
604 }
605 let member =
606 crate::organization::member::load_by_id(self.db().read(), organization_id, member_id)
607 .await?;
608 if actor_user_id == member.user_id {
609 if !role.self_assignable {
610 return Err(AuthError::Forbidden);
611 }
612 } else {
613 // 解绑系统角色必须与绑定同权:否则 Admin 绑定后没人能收回。
614 self.require_permission(actor_user_id, organization_id, "role.assign", request_id)
615 .await?;
616 if role.system {
617 let grants = grants_of(self.db().read(), role.id).await?;
618 self.require_grantable(actor_user_id, organization_id, &grants, request_id)
619 .await?;
620 }
621 }
622
623 let txn = self.db().write().begin().await?;
624 unbind_role(&txn, member_id, role.id).await?;
625 let version = bump_version(&txn, organization_id).await?;
626 let keys = role_keys_of(&txn, member_id).await?;
627 self.emit_role(
628 &txn,
629 organization_id,
630 actor_user_id,
631 serde_json::json!({
632 "member_id": member.id,
633 "role_key": &role.key,
634 "action": "unassign",
635 "version": version,
636 "request_id": request_id,
637 }),
638 request_id,
639 )
640 .await?;
641 txn.commit().await?;
642
643 self.invalidate_org(organization_id).await?;
644 Ok(keys)
645 }
646
647 /// 成员直挂权限:整表替换语义。`permission` 必须在白名单内,否则直接拒绝。
648 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, member_id = %member_id, request_id = %request_id.unwrap_or_default()))]
649 pub async fn set_member_permissions(
650 &self,
651 actor_user_id: i64,
652 organization_id: Uuid,
653 member_id: Uuid,
654 grants: Vec<PermissionGrant>,
655 request_id: Option<&str>,
656 ) -> AuthResult<Vec<String>> {
657 self.require_permission(
658 actor_user_id,
659 organization_id,
660 "role.permission.update",
661 request_id,
662 )
663 .await?;
664 let member =
665 crate::organization::member::load_by_id(self.db().read(), organization_id, member_id)
666 .await?;
667 if member.role == crate::models::space::OrgMemberRole::Owner {
668 return Err(AuthError::OrganizationOwnerRequired);
669 }
670 let grants = Self::validate_grants(&grants)?;
671 self.require_grantable(actor_user_id, organization_id, &grants, request_id)
672 .await?;
673
674 let txn = self.db().write().begin().await?;
675 crate::models::space::organization_member_permissions::Entity::delete_many()
676 .filter(
677 crate::models::space::organization_member_permissions::Column::MemberId
678 .eq(member_id),
679 )
680 .exec(&txn)
681 .await?;
682 for grant in &grants {
683 crate::models::space::organization_member_permissions::ActiveModel {
684 member_id: Set(member_id),
685 permission: Set(grant.permission.clone()),
686 effect: Set(grant.effect),
687 granted_by: Set(Some(actor_user_id)),
688 ..Default::default()
689 }
690 .insert(&txn)
691 .await?;
692 }
693 let version = bump_version(&txn, organization_id).await?;
694 self.emit_role(
695 &txn,
696 organization_id,
697 actor_user_id,
698 serde_json::json!({
699 "member_id": member_id,
700 "granted": grants.len(),
701 "version": version,
702 "request_id": request_id,
703 }),
704 request_id,
705 )
706 .await?;
707 txn.commit().await?;
708
709 self.invalidate_org(organization_id).await?;
710 self.member_permissions(member_id).await
711 }
712
713 pub(crate) async fn member_permissions(&self, member_id: Uuid) -> AuthResult<Vec<String>> {
714 let rows = crate::models::space::organization_member_permissions::Entity::find()
715 .filter(
716 crate::models::space::organization_member_permissions::Column::MemberId
717 .eq(member_id),
718 )
719 .order_by_asc(crate::models::space::organization_member_permissions::Column::Permission)
720 .all(self.db().read())
721 .await?;
722 Ok(rows
723 .into_iter()
724 .filter(|row| is_known_permission(&row.permission))
725 .map(|row| row.permission)
726 .collect())
727 }
728
729 /// 角色事件与业务写入同事务入队:提交时一起可见,回滚时一起消失。
730 pub(crate) async fn emit_role(
731 &self,
732 txn: &sea_orm::DatabaseTransaction,
733 organization_id: Uuid,
734 actor_user_id: i64,
735 payload: serde_json::Value,
736 _request_id: Option<&str>,
737 ) -> AuthResult<()> {
738 let labels = crate::events::Labels::new()
739 .with(label::ORGANIZATION_ID, organization_id)
740 .with(label::ACTOR_USER_ID, actor_user_id);
741 self.emit_tx(txn, label::ORGANIZATION_ROLE_UPDATE, labels, payload, None)
742 .await
743 }
744
745 /// 单个角色详情。
746 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
747 pub async fn get_role(
748 &self,
749 actor_user_id: i64,
750 organization_id: Uuid,
751 role_id: Uuid,
752 request_id: Option<&str>,
753 ) -> AuthResult<RoleSummary> {
754 self.require_permission(actor_user_id, organization_id, "role.view", request_id)
755 .await?;
756 let row = load_role(self.db().read(), organization_id, role_id).await?;
757 self.role_summary(&row).await
758 }
759
760 /// 自助加入:只接受 `self_assignable = true` 的非内置角色。
761 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
762 pub async fn join_role(
763 &self,
764 actor_user_id: i64,
765 organization_id: Uuid,
766 role_id: Uuid,
767 request_id: Option<&str>,
768 ) -> AuthResult<MemberSummary> {
769 let role = load_role(self.db().read(), organization_id, role_id).await?;
770 if role.system || !role.self_assignable {
771 return Err(AuthError::Forbidden);
772 }
773 let row = member::find_active(self.db().read(), organization_id, actor_user_id)
774 .await?
775 .ok_or(AuthError::Forbidden)?;
776
777 let txn = self.db().write().begin().await?;
778 bind_role(&txn, row.id, role_id, actor_user_id).await?;
779 let version = bump_version(&txn, organization_id).await?;
780 let refreshed = member::load_by_id(&txn, organization_id, row.id).await?;
781 self.emit_member(
782 &txn,
783 organization_id,
784 actor_user_id,
785 serde_json::json!({
786 "id": row.id,
787 "user_id": actor_user_id,
788 "action": "join_role",
789 "role_id": role_id,
790 "request_id": request_id,
791 }),
792 request_id,
793 )
794 .await?;
795 txn.commit().await?;
796
797 self.invalidate_org(organization_id).await?;
798 let mut summary = self.member_summary(&refreshed).await?;
799 summary.version = version;
800 Ok(summary)
801 }
802
803 /// 自助退出:同样只接受自助角色;重复退出视为成功。
804 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, role_id = %role_id, request_id = %request_id.unwrap_or_default()))]
805 pub async fn leave_role(
806 &self,
807 actor_user_id: i64,
808 organization_id: Uuid,
809 role_id: Uuid,
810 request_id: Option<&str>,
811 ) -> AuthResult<MemberSummary> {
812 let role = load_role(self.db().read(), organization_id, role_id).await?;
813 if role.system || !role.self_assignable {
814 return Err(AuthError::Forbidden);
815 }
816 let row = member::find_active(self.db().read(), organization_id, actor_user_id)
817 .await?
818 .ok_or(AuthError::Forbidden)?;
819
820 let txn = self.db().write().begin().await?;
821 unbind_role(&txn, row.id, role_id).await?;
822 let version = bump_version(&txn, organization_id).await?;
823 let refreshed = member::load_by_id(&txn, organization_id, row.id).await?;
824 self.emit_member(
825 &txn,
826 organization_id,
827 actor_user_id,
828 serde_json::json!({
829 "id": row.id,
830 "user_id": actor_user_id,
831 "action": "leave_role",
832 "role_id": role_id,
833 "request_id": request_id,
834 }),
835 request_id,
836 )
837 .await?;
838 txn.commit().await?;
839
840 self.invalidate_org(organization_id).await?;
841 let mut summary = self.member_summary(&refreshed).await?;
842 summary.version = version;
843 Ok(summary)
844 }
845}
846
847async fn load_role<C: ConnectionTrait>(
848 db: &C,
849 organization_id: Uuid,
850 role_id: Uuid,
851) -> AuthResult<organization_roles::Model> {
852 organization_roles::Entity::find_by_id(role_id)
853 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
854 .one(db)
855 .await?
856 .ok_or(AuthError::OrganizationRoleNotFound)
857}
Added lib/auth/src/organization/service.rs lib/auth/src/organization/service.rs +1036 −0 View lib/auth/src/organization/service.rs
@@ -0,0 +1,1036 @@
1use chrono::Utc;
2use sea_orm::entity::Set;
3use sea_orm::sea_query::{Expr, ExprTrait};
4use sea_orm::{
5 ActiveModelTrait, ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
6 QuerySelect, TransactionTrait,
7};
8use uuid::Uuid;
9
10use crate::AuthSvc;
11use crate::errors::{AuthError, AuthResult};
12use crate::events::{Labels, label};
13use crate::models::space::{
14 OrgMemberRole, OrganizationStatus, PermissionEffect, organization_invite_redemptions,
15 organization_invites, organization_member_permissions, organization_member_roles,
16 organization_members, organization_role_permissions, organization_roles, organizations,
17};
18use crate::models::users::users;
19use crate::organization::types::{
20 CreateOrganizationBody, MAX_ORG_DESCRIPTION_LEN, MAX_ORG_NAME_LEN, MAX_PERMISSION_LEN,
21 MAX_ROLE_KEY_LEN, MAX_ROLE_NAME_LEN, MAX_SLUG_LEN, MemberSummary, OrganizationSummary,
22 PermissionGrant, RoleSummary, SYSTEM_ROLE_ADMIN, SYSTEM_ROLE_OWNER, UpdateOrganizationBody,
23 is_known_permission,
24};
25use crate::organization::{error, member, permission, role};
26
27pub(crate) fn perm_cache_prefix(organization_id: Uuid, user_id: i64) -> String {
28 format!("auth:org:perm:{organization_id}:{user_id}")
29}
30
31impl AuthSvc {
32 /// 组织写操作的统一收尾:推进 version,然后清掉本组织全部派生缓存。
33 /// version 同时是权限缓存的版本键与乐观锁基准,必须与写操作同事务推进。
34 pub(crate) async fn invalidate_org(&self, organization_id: Uuid) -> AuthResult<()> {
35 let members = organization_members::Entity::find()
36 .select_only()
37 .column(organization_members::Column::UserId)
38 .filter(organization_members::Column::OrganizationId.eq(organization_id))
39 .into_tuple::<i64>()
40 .all(self.db().read())
41 .await?;
42 self.drop_org_cache(organization_id, members).await
43 }
44
45 /// 成员行已随事务删除时用这一版:user_id 由调用方在事务内收集后传入。
46 pub(crate) async fn drop_org_cache(
47 &self,
48 organization_id: Uuid,
49 members: Vec<i64>,
50 ) -> AuthResult<()> {
51 let cache = self.cache();
52 for key in [
53 format!("auth:org:{organization_id}"),
54 format!("auth:org:summary:{organization_id}"),
55 ] {
56 if let Err(error) = cache.delete(&key).await {
57 tracing::warn!(%error, %organization_id, "failed to drop the organization cache");
58 }
59 }
60 for user_id in members {
61 if let Err(error) = cache
62 .delete(&perm_cache_prefix(organization_id, user_id))
63 .await
64 {
65 tracing::warn!(%error, %organization_id, user_id, "failed to drop the permission cache");
66 }
67 }
68 Ok(())
69 }
70
71 pub(crate) async fn peek_org_version(&self, organization_id: Uuid) -> AuthResult<Option<i32>> {
72 Ok(organizations::Entity::find_by_id(organization_id)
73 .select_only()
74 .column(organizations::Column::Version)
75 .into_tuple::<i32>()
76 .one(self.db().read())
77 .await?)
78 }
79
80 /// `expected_version <= 0` 表示调用方不做乐观锁校验。
81 pub(crate) async fn check_version(
82 &self,
83 organization_id: Uuid,
84 expected: i32,
85 ) -> AuthResult<()> {
86 if expected <= 0 {
87 return Ok(());
88 }
89 let current = self
90 .peek_org_version(organization_id)
91 .await?
92 .ok_or(AuthError::OrganizationNotFound)?;
93 if current != expected {
94 return Err(AuthError::VersionMismatch { current });
95 }
96 Ok(())
97 }
98
99 pub(crate) async fn is_platform_admin(&self, user_id: i64) -> AuthResult<bool> {
100 Ok(users::Entity::find_by_id(user_id)
101 .select_only()
102 .column(users::Column::IsAdmin)
103 .into_tuple::<bool>()
104 .one(self.db().read())
105 .await?
106 .unwrap_or(false))
107 }
108
109 /// 唯一权限闸门。所有组织内写操作先过这里,语义与 CheckPermission 完全一致。
110 pub(crate) async fn require_permission(
111 &self,
112 user_id: i64,
113 organization_id: Uuid,
114 permission: &str,
115 request_id: Option<&str>,
116 ) -> AuthResult<()> {
117 let outcome =
118 permission::resolve(self, organization_id, user_id, permission, request_id).await?;
119 if outcome.allowed {
120 return Ok(());
121 }
122 Err(error::reason_error(outcome.reason, permission))
123 }
124
125 /// 授权上限:只能授出自己当前确实持有的权限,不能借新建/改角色提权。
126 pub(crate) async fn require_grantable(
127 &self,
128 user_id: i64,
129 organization_id: Uuid,
130 grants: &[PermissionGrant],
131 request_id: Option<&str>,
132 ) -> AuthResult<()> {
133 for grant in grants {
134 if grant.effect == PermissionEffect::Allow {
135 self.require_permission(user_id, organization_id, &grant.permission, request_id)
136 .await?;
137 }
138 }
139 Ok(())
140 }
141
142 /// 组织事件在业务事务**内部**入队:业务写入与事件行同生共死。
143 ///
144 /// 写不进 outbox 就让整个事务回滚(`AuthError` 向上传播),这是刻意选择:
145 /// 宁可这次写操作失败重试,也不要出现「组织改成了、消费方永远不知道」。
146 async fn emit_org<T: serde::Serialize>(
147 &self,
148 txn: &sea_orm::DatabaseTransaction,
149 kind: &str,
150 organization_id: Uuid,
151 actor_user_id: i64,
152 payload: T,
153 request_id: Option<&str>,
154 ) -> AuthResult<()> {
155 let labels = Labels::new()
156 .with(label::ORGANIZATION_ID, organization_id)
157 .with(label::ACTOR_USER_ID, actor_user_id);
158 self.emit_tx(txn, kind, labels, payload, request_id).await
159 }
160
161 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, request_id = %request_id.unwrap_or_default()))]
162 pub async fn create_organization(
163 &self,
164 actor_user_id: i64,
165 body: CreateOrganizationBody,
166 request_id: Option<&str>,
167 ) -> AuthResult<OrganizationSummary> {
168 let slug = normalize_slug(&body.slug)?;
169 let name = normalize_name(&body.name)?;
170 let description = normalize_description(body.description.as_deref())?;
171 let avatar_url = normalize_avatar(body.avatar_url.as_deref())?;
172
173 users::Entity::find_by_id(actor_user_id)
174 .filter(users::Column::DeletedAt.is_null())
175 .one(self.db().read())
176 .await?
177 .ok_or(AuthError::UserNotFound)?;
178 if organizations::Entity::find()
179 .filter(organizations::Column::Slug.eq(slug.clone()))
180 .one(self.db().read())
181 .await?
182 .is_some()
183 {
184 return Err(AuthError::OrganizationSlugTaken);
185 }
186
187 let welcome_message = match body.welcome_message.as_deref() {
188 Some(value) if value.trim().is_empty() => None,
189 Some(value) => normalize_description(Some(value))?,
190 None => None,
191 };
192 let allow_join_level = valid_join_level(body.allow_join_level.unwrap_or(0))?;
193
194 let txn = self.db().write().begin().await?;
195 let organization = organizations::ActiveModel {
196 slug: Set(slug),
197 name: Set(name),
198 description: Set(description),
199 avatar_url: Set(avatar_url),
200 status: Set(OrganizationStatus::Active),
201 owner_user_id: Set(actor_user_id),
202 join_policy: Set(body
203 .join_policy
204 .unwrap_or(crate::models::space::OrganizationJoinPolicy::InviteOnly)),
205 allow_join: Set(body.allow_join.unwrap_or(false)),
206 require_email_verified: Set(body.require_email_verified.unwrap_or(true)),
207 require_mfa: Set(body.require_mfa.unwrap_or(false)),
208 welcome_message: Set(welcome_message),
209 member_must_enable_2fa: Set(body.member_must_enable_2fa.unwrap_or(false)),
210 allow_invite: Set(body.allow_invite.unwrap_or(true)),
211 allow_join_level: Set(allow_join_level),
212 ..Default::default()
213 }
214 .insert(&txn)
215 .await
216 .map_err(error::map_insert_conflict)?;
217
218 let owner_role = role::seed_system_roles(&txn, organization.id).await?;
219 let membership = member::insert_owner(&txn, organization.id, actor_user_id).await?;
220 role::bind_role(&txn, membership.id, owner_role, actor_user_id).await?;
221 self.emit_org(
222 &txn,
223 label::ORGANIZATION_LIFE_CYCLE_CREATE,
224 organization.id,
225 actor_user_id,
226 serde_json::json!({
227 "id": organization.id,
228 "slug": &organization.slug,
229 "name": &organization.name,
230 "request_id": request_id,
231 }),
232 request_id,
233 )
234 .await?;
235 txn.commit().await?;
236
237 let summary = self
238 .organization_summary(&organization, Some(actor_user_id))
239 .await?;
240 Ok(summary)
241 }
242
243 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = ?organization_id, request_id = %request_id.unwrap_or_default()))]
244 pub async fn get_organization(
245 &self,
246 actor_user_id: i64,
247 organization_id: Option<Uuid>,
248 slug: Option<&str>,
249 request_id: Option<&str>,
250 ) -> AuthResult<(OrganizationSummary, Option<MemberSummary>)> {
251 let organization = match (
252 organization_id,
253 slug.map(str::trim).filter(|value| !value.is_empty()),
254 ) {
255 (Some(id), _) => {
256 organizations::Entity::find_by_id(id)
257 .filter(organizations::Column::DeletedAt.is_null())
258 .one(self.db().read())
259 .await?
260 }
261 (None, Some(slug)) => {
262 organizations::Entity::find()
263 .filter(organizations::Column::Slug.eq(slug.to_owned()))
264 .filter(organizations::Column::DeletedAt.is_null())
265 .one(self.db().read())
266 .await?
267 }
268 (None, None) => None,
269 }
270 .ok_or(AuthError::OrganizationNotFound)?;
271
272 let summary = self
273 .organization_summary(&organization, Some(actor_user_id))
274 .await?;
275 // 非成员只看公开字段:my_role 为空即可,不额外补一层 DTO。
276 let detail =
277 match member::find_any(self.db().read(), organization.id, actor_user_id).await? {
278 Some(row) => Some(self.member_summary(&row).await?),
279 None => None,
280 };
281 Ok((summary, detail))
282 }
283
284 pub(crate) async fn organization_summary(
285 &self,
286 organization: &organizations::Model,
287 actor_user_id: Option<i64>,
288 ) -> AuthResult<OrganizationSummary> {
289 let member_count = organization_members::Entity::find()
290 .filter(organization_members::Column::OrganizationId.eq(organization.id))
291 .filter(organization_members::Column::LeftAt.is_null())
292 .filter(organization_members::Column::BannedAt.is_null())
293 .count(self.db().read())
294 .await? as i64;
295 let my_role = match actor_user_id {
296 Some(user_id) => member::find_active(self.db().read(), organization.id, user_id)
297 .await?
298 .map(|row| row.role),
299 None => None,
300 };
301 Ok(OrganizationSummary {
302 id: organization.id,
303 slug: organization.slug.clone(),
304 name: organization.name.clone(),
305 description: organization.description.clone(),
306 avatar_url: organization.avatar_url.clone(),
307 status: organization.status,
308 owner_user_id: organization.owner_user_id,
309 member_count,
310 version: organization.version,
311 created_at: organization.created_at,
312 updated_at: organization.updated_at,
313 archived_at: organization.archived_at,
314 my_role,
315 join_policy: organization.join_policy,
316 allow_join: organization.allow_join,
317 require_email_verified: organization.require_email_verified,
318 require_mfa: organization.require_mfa,
319 welcome_message: organization.welcome_message.clone(),
320 member_must_enable_2fa: organization.member_must_enable_2fa,
321 allow_invite: organization.allow_invite,
322 allow_join_level: organization.allow_join_level,
323 })
324 }
325
326 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, user_id = user_id, request_id = %request_id.unwrap_or_default()))]
327 pub async fn list_organizations(
328 &self,
329 actor_user_id: i64,
330 user_id: i64,
331 include_archived: bool,
332 limit: u64,
333 offset: u64,
334 request_id: Option<&str>,
335 ) -> AuthResult<Vec<OrganizationSummary>> {
336 let target = if user_id == 0 { actor_user_id } else { user_id };
337 if target != actor_user_id && !self.is_platform_admin(actor_user_id).await? {
338 return Err(AuthError::Forbidden);
339 }
340
341 let ids = organization_members::Entity::find()
342 .select_only()
343 .column(organization_members::Column::OrganizationId)
344 .filter(organization_members::Column::UserId.eq(target))
345 .filter(organization_members::Column::LeftAt.is_null())
346 .filter(organization_members::Column::BannedAt.is_null())
347 .into_tuple::<Uuid>()
348 .all(self.db().read())
349 .await?;
350 if ids.is_empty() {
351 return Ok(Vec::new());
352 }
353
354 let mut query = organizations::Entity::find()
355 .filter(organizations::Column::DeletedAt.is_null())
356 .filter(organizations::Column::Id.is_in(ids));
357 if !include_archived {
358 query = query.filter(organizations::Column::Status.ne(OrganizationStatus::Archived));
359 }
360 let rows = query
361 .order_by_desc(organizations::Column::UpdatedAt)
362 .limit(limit)
363 .offset(offset)
364 .all(self.db().read())
365 .await?;
366
367 let mut out = Vec::with_capacity(rows.len());
368 for row in rows {
369 out.push(self.organization_summary(&row, Some(target)).await?);
370 }
371 Ok(out)
372 }
373
374 /// 公开目录:ACTIVE 且非 PRIVATE 的组织。不要求成员身份。
375 pub async fn list_public_organizations(
376 &self,
377 query: &str,
378 limit: u64,
379 offset: u64,
380 ) -> AuthResult<Vec<OrganizationSummary>> {
381 let mut select = organizations::Entity::find()
382 .filter(organizations::Column::DeletedAt.is_null())
383 .filter(organizations::Column::Status.eq(OrganizationStatus::Active))
384 .filter(
385 organizations::Column::JoinPolicy
386 .ne(crate::models::space::OrganizationJoinPolicy::Private),
387 );
388 let needle = query.trim();
389 if !needle.is_empty() {
390 select = select.filter(
391 Condition::any()
392 .add(organizations::Column::Slug.contains(needle))
393 .add(organizations::Column::Name.contains(needle)),
394 );
395 }
396 let rows = select
397 .order_by_desc(organizations::Column::CreatedAt)
398 .limit(limit)
399 .offset(offset)
400 .all(self.db().read())
401 .await?;
402 let mut out = Vec::with_capacity(rows.len());
403 for row in rows {
404 out.push(self.organization_summary(&row, None).await?);
405 }
406 Ok(out)
407 }
408
409 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
410 pub async fn update_organization(
411 &self,
412 actor_user_id: i64,
413 organization_id: Uuid,
414 body: UpdateOrganizationBody,
415 request_id: Option<&str>,
416 ) -> AuthResult<OrganizationSummary> {
417 self.require_permission(actor_user_id, organization_id, "org.update", request_id)
418 .await?;
419 self.check_version(organization_id, body.expected_version)
420 .await?;
421
422 let name = body.name.as_deref().map(normalize_name).transpose()?;
423 let description = match body.description.as_deref() {
424 Some(value) => Some(normalize_description(Some(value))?),
425 None => None,
426 };
427 let avatar_url = match body.avatar_url.as_deref() {
428 Some(value) => Some(normalize_avatar(Some(value))?),
429 None => None,
430 };
431
432 let txn = self.db().write().begin().await?;
433 let current = organizations::Entity::find_by_id(organization_id)
434 .filter(organizations::Column::DeletedAt.is_null())
435 .one(&txn)
436 .await?
437 .ok_or(AuthError::OrganizationNotFound)?;
438 if body.expected_version > 0 && current.version != body.expected_version {
439 return Err(AuthError::VersionMismatch {
440 current: current.version,
441 });
442 }
443 let mut active: organizations::ActiveModel = current.into();
444 if let Some(value) = name {
445 active.name = Set(value);
446 }
447 if let Some(value) = description {
448 active.description = Set(value);
449 }
450 if let Some(value) = avatar_url {
451 active.avatar_url = Set(value);
452 }
453 if let Some(value) = body.join_policy {
454 active.join_policy = Set(value);
455 }
456 if let Some(value) = body.allow_join {
457 active.allow_join = Set(value);
458 }
459 if let Some(value) = body.require_email_verified {
460 active.require_email_verified = Set(value);
461 }
462 if let Some(value) = body.require_mfa {
463 active.require_mfa = Set(value);
464 }
465 if let Some(value) = body.welcome_message.clone() {
466 let trimmed = value.trim();
467 active.welcome_message = Set(if trimmed.is_empty() {
468 None
469 } else {
470 Some(trimmed.to_string())
471 });
472 }
473 if let Some(value) = body.member_must_enable_2fa {
474 active.member_must_enable_2fa = Set(value);
475 }
476 if let Some(value) = body.allow_invite {
477 active.allow_invite = Set(value);
478 }
479 if let Some(value) = body.allow_join_level {
480 active.allow_join_level = Set(valid_join_level(value)?);
481 }
482 let updated = active.update(&txn).await?;
483 let version = bump_version(&txn, organization_id).await?;
484 self.emit_org(
485 &txn,
486 label::ORGANIZATION_LIFE_CYCLE_UPDATE,
487 organization_id,
488 actor_user_id,
489 serde_json::json!({
490 "id": organization_id,
491 "name": &updated.name,
492 "request_id": request_id,
493 }),
494 request_id,
495 )
496 .await?;
497 txn.commit().await?;
498
499 self.invalidate_org(organization_id).await?;
500 let refreshed = self.load_organization(organization_id).await?;
501 let mut summary = self
502 .organization_summary(&refreshed, Some(actor_user_id))
503 .await?;
504 summary.version = version;
505 Ok(summary)
506 }
507
508 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
509 pub async fn archive_organization(
510 &self,
511 actor_user_id: i64,
512 organization_id: Uuid,
513 reason: Option<&str>,
514 request_id: Option<&str>,
515 ) -> AuthResult<OrganizationSummary> {
516 self.require_permission(actor_user_id, organization_id, "org.archive", request_id)
517 .await?;
518 let reason = normalize_reason(reason)?;
519
520 let txn = self.db().write().begin().await?;
521 organizations::Entity::update_many()
522 .col_expr(
523 organizations::Column::Status,
524 Expr::value(OrganizationStatus::Archived),
525 )
526 .col_expr(organizations::Column::ArchivedAt, Expr::value(Utc::now()))
527 .col_expr(organizations::Column::SuspendedReason, Expr::value(reason))
528 .filter(organizations::Column::Id.eq(organization_id))
529 .filter(organizations::Column::DeletedAt.is_null())
530 .exec(&txn)
531 .await?;
532 let version = bump_version(&txn, organization_id).await?;
533 self.emit_org(
534 &txn,
535 label::ORGANIZATION_LIFE_CYCLE_UPDATE,
536 organization_id,
537 actor_user_id,
538 serde_json::json!({
539 "id": organization_id,
540 "action": "archive",
541 "request_id": request_id,
542 }),
543 request_id,
544 )
545 .await?;
546 txn.commit().await?;
547
548 self.invalidate_org(organization_id).await?;
549 let refreshed = self.load_organization(organization_id).await?;
550 let mut summary = self
551 .organization_summary(&refreshed, Some(actor_user_id))
552 .await?;
553 summary.version = version;
554 Ok(summary)
555 }
556
557 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, request_id = %request_id.unwrap_or_default()))]
558 pub async fn restore_organization(
559 &self,
560 actor_user_id: i64,
561 organization_id: Uuid,
562 request_id: Option<&str>,
563 ) -> AuthResult<OrganizationSummary> {
564 // 归档组织的权限判定一律不通过,所以这里不能用 require_permission:
565 // 恢复动作由 OWNER 或平台管理员执行。
566 let organization = self.load_organization(organization_id).await?;
567 if organization.owner_user_id != actor_user_id
568 && !self.is_platform_admin(actor_user_id).await?
569 {
570 return Err(AuthError::Forbidden);
571 }
572
573 let txn = self.db().write().begin().await?;
574 organizations::Entity::update_many()
575 .col_expr(
576 organizations::Column::Status,
577 Expr::value(OrganizationStatus::Active),
578 )
579 .col_expr(
580 organizations::Column::ArchivedAt,
581 Expr::value(None::<chrono::DateTime<Utc>>),
582 )
583 .col_expr(
584 organizations::Column::SuspendedReason,
585 Expr::value(None::<String>),
586 )
587 .filter(organizations::Column::Id.eq(organization_id))
588 .exec(&txn)
589 .await?;
590 let version = bump_version(&txn, organization_id).await?;
591 self.emit_org(
592 &txn,
593 label::ORGANIZATION_LIFE_CYCLE_UPDATE,
594 organization_id,
595 actor_user_id,
596 serde_json::json!({
597 "id": organization_id,
598 "action": "restore",
599 "request_id": request_id,
600 }),
601 request_id,
602 )
603 .await?;
604 txn.commit().await?;
605
606 self.invalidate_org(organization_id).await?;
607 let refreshed = self.load_organization(organization_id).await?;
608 let mut summary = self
609 .organization_summary(&refreshed, Some(actor_user_id))
610 .await?;
611 summary.version = version;
612 Ok(summary)
613 }
614
615 /// 删除分两种语义:`force = false` 时仍有活跃成员会被拦下,
616 /// `force = true` 时才连成员与角色一并清理。软删标记写在 `deleted_at` 上,
617 /// 但权限缓存与派生数据必须真的清掉,否则残留的授予会在重建同名组织时复活。
618 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, force = force, request_id = %request_id.unwrap_or_default()))]
619 pub async fn delete_organization(
620 &self,
621 actor_user_id: i64,
622 organization_id: Uuid,
623 force: bool,
624 reason: Option<&str>,
625 request_id: Option<&str>,
626 ) -> AuthResult<bool> {
627 let organization = self.load_organization(organization_id).await?;
628 if organization.owner_user_id != actor_user_id
629 && !self.is_platform_admin(actor_user_id).await?
630 {
631 return Err(AuthError::Forbidden);
632 }
633 let reason = normalize_reason(reason)?;
634
635 if !force {
636 let active = organization_members::Entity::find()
637 .filter(organization_members::Column::OrganizationId.eq(organization_id))
638 .filter(organization_members::Column::LeftAt.is_null())
639 .filter(organization_members::Column::BannedAt.is_null())
640 .count(self.db().read())
641 .await?;
642 if active > 0 {
643 return Err(AuthError::OrganizationHasMembers);
644 }
645 }
646
647 let txn = self.db().write().begin().await?;
648 let member_ids = organization_members::Entity::find()
649 .select_only()
650 .column(organization_members::Column::Id)
651 .filter(organization_members::Column::OrganizationId.eq(organization_id))
652 .into_tuple::<Uuid>()
653 .all(&txn)
654 .await?;
655 // 权限缓存键依赖 user_id,而成员行在本事务里会被物理删除,所以先把 user_id 取出来。
656 let member_user_ids = organization_members::Entity::find()
657 .select_only()
658 .column(organization_members::Column::UserId)
659 .filter(organization_members::Column::OrganizationId.eq(organization_id))
660 .into_tuple::<i64>()
661 .all(&txn)
662 .await?;
663 if !member_ids.is_empty() {
664 organization_member_permissions::Entity::delete_many()
665 .filter(organization_member_permissions::Column::MemberId.is_in(member_ids.clone()))
666 .exec(&txn)
667 .await?;
668 organization_member_roles::Entity::delete_many()
669 .filter(organization_member_roles::Column::MemberId.is_in(member_ids))
670 .exec(&txn)
671 .await?;
672 organization_members::Entity::delete_many()
673 .filter(organization_members::Column::OrganizationId.eq(organization_id))
674 .exec(&txn)
675 .await?;
676 }
677 organization_invite_redemptions::Entity::delete_many()
678 .filter(organization_invite_redemptions::Column::OrganizationId.eq(organization_id))
679 .exec(&txn)
680 .await?;
681 organization_invites::Entity::delete_many()
682 .filter(organization_invites::Column::OrganizationId.eq(organization_id))
683 .exec(&txn)
684 .await?;
685 let role_ids = organization_roles::Entity::find()
686 .select_only()
687 .column(organization_roles::Column::Id)
688 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
689 .into_tuple::<Uuid>()
690 .all(&txn)
691 .await?;
692 if !role_ids.is_empty() {
693 organization_role_permissions::Entity::delete_many()
694 .filter(organization_role_permissions::Column::RoleId.is_in(role_ids))
695 .exec(&txn)
696 .await?;
697 }
698 organization_roles::Entity::delete_many()
699 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
700 .exec(&txn)
701 .await?;
702 organizations::Entity::update_many()
703 .col_expr(organizations::Column::DeletedAt, Expr::value(Utc::now()))
704 .col_expr(
705 organizations::Column::SuspendedReason,
706 Expr::value(reason.clone()),
707 )
708 .filter(organizations::Column::Id.eq(organization_id))
709 .exec(&txn)
710 .await?;
711 bump_version(&txn, organization_id).await?;
712 self.emit_org(
713 &txn,
714 label::ORGANIZATION_LIFE_CYCLE_DELETE,
715 organization_id,
716 actor_user_id,
717 serde_json::json!({
718 "id": organization_id,
719 "force": force,
720 "reason": &reason,
721 "request_id": request_id,
722 }),
723 request_id,
724 )
725 .await?;
726 txn.commit().await?;
727
728 self.drop_org_cache(organization_id, member_user_ids)
729 .await?;
730 Ok(true)
731 }
732
733 #[tracing::instrument(skip_all, fields(actor_user_id = actor_user_id, organization_id = %organization_id, target_user_id = target_user_id, request_id = %request_id.unwrap_or_default()))]
734 pub async fn transfer_ownership(
735 &self,
736 actor_user_id: i64,
737 organization_id: Uuid,
738 target_user_id: i64,
739 request_id: Option<&str>,
740 ) -> AuthResult<OrganizationSummary> {
741 let organization = self.load_organization(organization_id).await?;
742 if organization.owner_user_id != actor_user_id
743 && !self.is_platform_admin(actor_user_id).await?
744 {
745 return Err(AuthError::Forbidden);
746 }
747 if organization.status != OrganizationStatus::Active {
748 return Err(AuthError::OrganizationInactive);
749 }
750 if target_user_id == organization.owner_user_id {
751 return Err(AuthError::OrganizationMemberExists);
752 }
753 let target = member::find_active(self.db().read(), organization_id, target_user_id)
754 .await?
755 .ok_or(AuthError::OrganizationMemberNotFound)?;
756 let owner_role = role::find_by_key(organization_id, SYSTEM_ROLE_OWNER, self.db().read())
757 .await?
758 .ok_or_else(|| AuthError::internal("owner role is missing"))?;
759 let admin_role = role::find_by_key(organization_id, SYSTEM_ROLE_ADMIN, self.db().read())
760 .await?
761 .ok_or_else(|| AuthError::internal("admin role is missing"))?;
762
763 let txn = self.db().write().begin().await?;
764 // 唯一 OWNER 的不变式落在两处:organizations.owner_user_id 与成员行的粗粒度角色。
765 // 两个改写放在同一事务里,读到中间态的可能性为零。
766 if let Some(previous) =
767 member::find_any(&txn, organization_id, organization.owner_user_id).await?
768 {
769 let mut active: organization_members::ActiveModel = previous.clone().into();
770 active.role = Set(OrgMemberRole::Admin);
771 active.update(&txn).await?;
772 role::unbind_role(&txn, previous.id, owner_role.id).await?;
773 role::bind_role(&txn, previous.id, admin_role.id, actor_user_id).await?;
774 }
775 let mut active: organization_members::ActiveModel = target.clone().into();
776 active.role = Set(OrgMemberRole::Owner);
777 active.update(&txn).await?;
778 role::unbind_role(&txn, target.id, admin_role.id).await?;
779 role::bind_role(&txn, target.id, owner_role.id, actor_user_id).await?;
780 organizations::Entity::update_many()
781 .col_expr(
782 organizations::Column::OwnerUserId,
783 Expr::value(target_user_id),
784 )
785 .filter(organizations::Column::Id.eq(organization_id))
786 .exec(&txn)
787 .await?;
788 let version = bump_version(&txn, organization_id).await?;
789 self.emit_org(
790 &txn,
791 label::ORGANIZATION_MEMBER_UPDATE,
792 organization_id,
793 actor_user_id,
794 serde_json::json!({
795 "id": organization_id,
796 "action": "transfer_ownership",
797 "previous_owner": organization.owner_user_id,
798 "new_owner": target_user_id,
799 "request_id": request_id,
800 }),
801 request_id,
802 )
803 .await?;
804 txn.commit().await?;
805
806 self.invalidate_org(organization_id).await?;
807 let refreshed = self.load_organization(organization_id).await?;
808 let mut summary = self
809 .organization_summary(&refreshed, Some(actor_user_id))
810 .await?;
811 summary.version = version;
812 Ok(summary)
813 }
814
815 pub(crate) async fn load_organization(
816 &self,
817 organization_id: Uuid,
818 ) -> AuthResult<organizations::Model> {
819 organizations::Entity::find_by_id(organization_id)
820 .filter(organizations::Column::DeletedAt.is_null())
821 .one(self.db().read())
822 .await?
823 .ok_or(AuthError::OrganizationNotFound)
824 }
825
826 pub(crate) async fn member_summary(
827 &self,
828 row: &organization_members::Model,
829 ) -> AuthResult<MemberSummary> {
830 let user = users::Entity::find_by_id(row.user_id)
831 .one(self.db().read())
832 .await?;
833 Ok(MemberSummary {
834 id: row.id,
835 organization_id: row.organization_id,
836 user_id: row.user_id,
837 username: user
838 .as_ref()
839 .map(|u| u.username.clone())
840 .unwrap_or_default(),
841 displayname: user.and_then(|u| u.displayname),
842 role: row.role,
843 role_keys: role::role_keys_of(self.db().read(), row.id).await?,
844 permissions: permission::effective(self.db().read(), row).await?,
845 joined_at: row.joined_at,
846 left_at: row.left_at,
847 banned_at: row.banned_at,
848 invited_by: row.invited_by,
849 join_method: row.join_method,
850 alias_name: row.alias_name.clone(),
851 alias_avatar_url: row.alias_avatar_url.clone(),
852 message_notifications: row.message_notifications,
853 muted: row.muted,
854 mute_until: row.mute_until,
855 mobile_push: row.mobile_push,
856 suppress_everyone: row.suppress_everyone,
857 suppress_roles: row.suppress_roles,
858 hide_muted_channels: row.hide_muted_channels,
859 ban_reason: row.ban_reason.clone(),
860 ban_expires_at: row.ban_expires_at,
861 ban_allow_agent_join: row.ban_allow_agent_join,
862 ban_allow_speak: row.ban_allow_speak,
863 ban_allow_push_repo: row.ban_allow_push_repo,
864 ban_allow_comment_any: row.ban_allow_comment_any,
865 version: row.version,
866 last_active_at: row.last_active_at,
867 })
868 }
869
870 pub(crate) async fn role_summary(
871 &self,
872 row: &organization_roles::Model,
873 ) -> AuthResult<RoleSummary> {
874 let permissions = role::grants_of(self.db().read(), row.id).await?;
875 let member_count = organization_member_roles::Entity::find()
876 .filter(organization_member_roles::Column::RoleId.eq(row.id))
877 .count(self.db().read())
878 .await? as i64;
879 Ok(RoleSummary {
880 id: row.id,
881 organization_id: row.organization_id,
882 key: row.key.clone(),
883 name: row.name.clone(),
884 description: row.description.clone(),
885 rank: row.rank,
886 system: row.system,
887 default_role: row.default_role,
888 self_assignable: row.self_assignable,
889 permissions,
890 member_count,
891 created_at: row.created_at,
892 updated_at: row.updated_at,
893 version: row.version,
894 color: row.color.clone(),
895 emoji: row.emoji.clone(),
896 icon_hash: row.icon_hash.clone(),
897 position: row.position,
898 })
899 }
900
901 pub(crate) async fn default_role_id(&self, organization_id: Uuid) -> AuthResult<Option<Uuid>> {
902 Ok(organization_roles::Entity::find()
903 .filter(organization_roles::Column::OrganizationId.eq(organization_id))
904 .filter(organization_roles::Column::DefaultRole.eq(true))
905 .order_by_asc(organization_roles::Column::Rank)
906 .one(self.db().read())
907 .await?
908 .map(|row| row.id))
909 }
910
911 pub(crate) fn validate_grants(grants: &[PermissionGrant]) -> AuthResult<Vec<PermissionGrant>> {
912 let mut out = Vec::with_capacity(grants.len());
913 for grant in grants {
914 let permission = grant.permission.trim();
915 if permission.is_empty()
916 || permission.len() > MAX_PERMISSION_LEN
917 || !is_known_permission(permission)
918 {
919 return Err(AuthError::InvalidPermission {
920 permission: grant.permission.clone(),
921 });
922 }
923 out.push(PermissionGrant {
924 permission: permission.to_owned(),
925 effect: match grant.effect {
926 PermissionEffect::Deny => PermissionEffect::Deny,
927 _ => PermissionEffect::Allow,
928 },
929 });
930 }
931 Ok(out)
932 }
933}
934
935/// 在写事务内推进 version 并回读新值。
936pub(crate) async fn bump_version<C>(db: &C, organization_id: Uuid) -> AuthResult<i32>
937where
938 C: sea_orm::ConnectionTrait,
939{
940 let bumped = organizations::Entity::update_many()
941 .col_expr(
942 organizations::Column::Version,
943 Expr::col(organizations::Column::Version).add(1),
944 )
945 .col_expr(organizations::Column::UpdatedAt, Expr::value(Utc::now()))
946 .filter(organizations::Column::Id.eq(organization_id))
947 .exec(db)
948 .await?;
949 if bumped.rows_affected == 0 {
950 return Err(AuthError::OrganizationNotFound);
951 }
952 organizations::Entity::find_by_id(organization_id)
953 .select_only()
954 .column(organizations::Column::Version)
955 .into_tuple::<i32>()
956 .one(db)
957 .await?
958 .ok_or(AuthError::OrganizationNotFound)
959}
960
961pub(crate) fn normalize_slug(raw: &str) -> AuthResult<String> {
962 let slug = raw.trim().to_lowercase();
963 if slug.is_empty()
964 || slug.len() > MAX_SLUG_LEN
965 || !slug
966 .chars()
967 .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-')
968 || slug.starts_with('-')
969 || slug.ends_with('-')
970 {
971 return Err(AuthError::MalformedRequest);
972 }
973 Ok(slug)
974}
975
976pub(crate) fn normalize_name(raw: &str) -> AuthResult<String> {
977 let name = raw.trim();
978 if name.is_empty() || name.len() > MAX_ORG_NAME_LEN {
979 return Err(AuthError::MalformedRequest);
980 }
981 Ok(name.to_owned())
982}
983
984pub(crate) fn normalize_description(raw: Option<&str>) -> AuthResult<Option<String>> {
985 match raw.map(str::trim) {
986 None | Some("") => Ok(None),
987 Some(value) if value.len() > MAX_ORG_DESCRIPTION_LEN => Err(AuthError::MalformedRequest),
988 Some(value) => Ok(Some(value.to_owned())),
989 }
990}
991
992pub(crate) fn normalize_avatar(raw: Option<&str>) -> AuthResult<Option<String>> {
993 match raw.map(str::trim) {
994 None | Some("") => Ok(None),
995 Some(value) if value.len() > 512 || !value.starts_with("https://") => {
996 Err(AuthError::MalformedRequest)
997 }
998 Some(value) => Ok(Some(value.to_owned())),
999 }
1000}
1001
1002pub(crate) fn normalize_reason(raw: Option<&str>) -> AuthResult<Option<String>> {
1003 match raw.map(str::trim) {
1004 None | Some("") => Ok(None),
1005 Some(value) if value.len() > 512 => Err(AuthError::MalformedRequest),
1006 Some(value) => Ok(Some(value.to_owned())),
1007 }
1008}
1009
1010pub(crate) fn normalize_role_key(raw: &str) -> AuthResult<String> {
1011 let key = raw.trim().to_lowercase();
1012 if key.is_empty()
1013 || key.len() > MAX_ROLE_KEY_LEN
1014 || !key
1015 .chars()
1016 .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-')
1017 {
1018 return Err(AuthError::MalformedRequest);
1019 }
1020 Ok(key)
1021}
1022
1023pub(crate) fn normalize_role_name(raw: &str) -> AuthResult<String> {
1024 let name = raw.trim();
1025 if name.is_empty() || name.len() > MAX_ROLE_NAME_LEN {
1026 return Err(AuthError::MalformedRequest);
1027 }
1028 Ok(name.to_owned())
1029}
1030
1031pub(crate) fn valid_join_level(value: i16) -> AuthResult<i16> {
1032 (0..=4)
1033 .contains(&value)
1034 .then_some(value)
1035 .ok_or(AuthError::MalformedRequest)
1036}
Added lib/auth/src/organization/types.rs lib/auth/src/organization/types.rs +422 −0 View lib/auth/src/organization/types.rs
@@ -0,0 +1,422 @@
1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Deserializer, Serialize};
3use utoipa::ToSchema;
4use uuid::Uuid;
5
6use crate::models::space::{
7 MessageNotifications, OrgJoinMethod, OrgMemberRole, OrganizationJoinPolicy,
8 OrganizationJoinRequestStatus, OrganizationStatus, PermissionEffect,
9};
10
11/// 组织内权限键白名单。写入口一律先过这里,未知键直接拒绝;
12/// 允许自由字符串会让权限判定变成"拼写是否正确"的赌博。
13pub const PERMISSIONS: &[&str] = &[
14 "org.view",
15 "org.update",
16 "org.archive",
17 "org.restore",
18 "org.delete",
19 "org.transfer_ownership",
20 "member.view",
21 "member.invite",
22 "member.add",
23 "member.remove",
24 "member.ban",
25 "member.role.update",
26 "role.view",
27 "role.create",
28 "role.update",
29 "role.delete",
30 "role.permission.update",
31 "role.assign",
32 "invite.view",
33 "invite.create",
34 "invite.revoke",
35 "audit.view",
36 "space.view_space",
37 "space.read",
38 "space.message",
39 "space.manage_members",
40 "space.manage_roles",
41 "space.manage_invites",
42 "space.manage_bans",
43 "space.manage_channels",
44 "space.manage_overwrites",
45];
46
47/// ADMIN 固有集。命中这里的键不看角色绑定,直接通过。
48/// 刻意不含 `org.delete` / `org.transfer_ownership`:这两个是 Owner 专属。
49pub const ADMIN_PERMISSIONS: &[&str] = &[
50 "org.view",
51 "org.update",
52 "org.archive",
53 "org.restore",
54 "member.view",
55 "member.invite",
56 "member.add",
57 "member.remove",
58 "member.ban",
59 "member.role.update",
60 "role.view",
61 "role.create",
62 "role.update",
63 "role.delete",
64 "role.permission.update",
65 "role.assign",
66 "invite.view",
67 "invite.create",
68 "invite.revoke",
69 "audit.view",
70 "space.view_space",
71 "space.read",
72 "space.message",
73 "space.manage_members",
74 "space.manage_roles",
75 "space.manage_invites",
76 "space.manage_bans",
77 "space.manage_channels",
78 "space.manage_overwrites",
79];
80
81pub const MAX_PERMISSION_LEN: usize = 64;
82pub const MAX_BATCH_PERMISSIONS: usize = 32;
83pub const MAX_SLUG_LEN: usize = 32;
84pub const MAX_ORG_NAME_LEN: usize = 128;
85pub const MAX_ORG_DESCRIPTION_LEN: usize = 1024;
86pub const MAX_ROLE_KEY_LEN: usize = 64;
87pub const MAX_ROLE_NAME_LEN: usize = 64;
88pub const MAX_REASON_LEN: usize = 512;
89pub const PERMISSION_CACHE_TTL_SECS: u64 = 300;
90pub const MAX_SLUG_ATTEMPTS: usize = 8;
91
92pub const SYSTEM_ROLE_OWNER: &str = "owner";
93pub const SYSTEM_ROLE_ADMIN: &str = "admin";
94pub const SYSTEM_ROLE_MEMBER: &str = "member";
95pub const DEFAULT_INVITE_TTL_SECS: u64 = 7 * 24 * 60 * 60;
96
97pub fn is_known_permission(permission: &str) -> bool {
98 PERMISSIONS.contains(&permission)
99}
100
101pub fn normalize_permission(permission: &str) -> Option<&'static str> {
102 let trimmed = permission.trim();
103 PERMISSIONS.iter().copied().find(|known| *known == trimmed)
104}
105
106pub fn is_admin_permission(permission: &str) -> bool {
107 ADMIN_PERMISSIONS.contains(&permission)
108}
109
110#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
111pub struct OrganizationSummary {
112 pub id: Uuid,
113 pub slug: String,
114 pub name: String,
115 pub description: Option<String>,
116 pub avatar_url: Option<String>,
117 pub status: OrganizationStatus,
118 pub owner_user_id: i64,
119 pub member_count: i64,
120 pub version: i32,
121 pub created_at: DateTime<Utc>,
122 pub updated_at: DateTime<Utc>,
123 pub archived_at: Option<DateTime<Utc>>,
124 pub my_role: Option<OrgMemberRole>,
125 pub join_policy: OrganizationJoinPolicy,
126 pub allow_join: bool,
127 pub require_email_verified: bool,
128 pub require_mfa: bool,
129 pub welcome_message: Option<String>,
130 pub member_must_enable_2fa: bool,
131 pub allow_invite: bool,
132 pub allow_join_level: i16,
133}
134
135#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
136pub struct MemberSummary {
137 pub id: Uuid,
138 pub organization_id: Uuid,
139 pub user_id: i64,
140 pub username: String,
141 pub displayname: Option<String>,
142 pub role: OrgMemberRole,
143 pub role_keys: Vec<String>,
144 pub permissions: Vec<String>,
145 pub joined_at: DateTime<Utc>,
146 pub left_at: Option<DateTime<Utc>>,
147 pub banned_at: Option<DateTime<Utc>>,
148 pub invited_by: Option<i64>,
149 pub join_method: OrgJoinMethod,
150 pub alias_name: Option<String>,
151 pub alias_avatar_url: Option<String>,
152 pub message_notifications: MessageNotifications,
153 pub muted: bool,
154 pub mute_until: Option<DateTime<Utc>>,
155 pub mobile_push: bool,
156 pub suppress_everyone: bool,
157 pub suppress_roles: bool,
158 pub hide_muted_channels: bool,
159 pub ban_reason: Option<String>,
160 pub ban_expires_at: Option<DateTime<Utc>>,
161 pub ban_allow_agent_join: bool,
162 pub ban_allow_speak: bool,
163 pub ban_allow_push_repo: bool,
164 pub ban_allow_comment_any: bool,
165 pub version: i32,
166 pub last_active_at: Option<DateTime<Utc>>,
167}
168
169#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
170pub struct PermissionGrant {
171 pub permission: String,
172 pub effect: PermissionEffect,
173}
174
175#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
176pub struct RoleSummary {
177 pub id: Uuid,
178 pub organization_id: Uuid,
179 pub key: String,
180 pub name: String,
181 pub description: Option<String>,
182 pub rank: i32,
183 pub system: bool,
184 pub default_role: bool,
185 pub self_assignable: bool,
186 pub permissions: Vec<PermissionGrant>,
187 pub member_count: i64,
188 pub created_at: DateTime<Utc>,
189 pub updated_at: DateTime<Utc>,
190 pub version: i32,
191 pub color: Option<String>,
192 pub emoji: Option<String>,
193 pub icon_hash: Option<String>,
194 pub position: i64,
195}
196
197#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
198pub struct InviteSummary {
199 pub id: Uuid,
200 pub organization_id: Uuid,
201 pub method: OrgJoinMethod,
202 pub role_id: Option<Uuid>,
203 pub target_user_id: Option<i64>,
204 pub created_by: i64,
205 pub max_uses: i32,
206 pub uses: i32,
207 pub expires_at: Option<DateTime<Utc>>,
208 pub revoked_at: Option<DateTime<Utc>>,
209 pub created_at: DateTime<Utc>,
210 pub email: Option<String>,
211}
212
213#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
214pub struct PermissionCheck {
215 pub allowed: bool,
216 pub reason: String,
217 pub matched_by: String,
218 pub organization_version: i32,
219}
220
221#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
222pub struct CreateOrganizationBody {
223 pub slug: String,
224 pub name: String,
225 pub description: Option<String>,
226 pub avatar_url: Option<String>,
227 #[serde(default)]
228 pub join_policy: Option<OrganizationJoinPolicy>,
229 #[serde(default)]
230 pub allow_join: Option<bool>,
231 #[serde(default)]
232 pub require_email_verified: Option<bool>,
233 #[serde(default)]
234 pub require_mfa: Option<bool>,
235 #[serde(default)]
236 pub welcome_message: Option<String>,
237 #[serde(default)]
238 pub member_must_enable_2fa: Option<bool>,
239 #[serde(default)]
240 pub allow_invite: Option<bool>,
241 #[serde(default)]
242 pub allow_join_level: Option<i16>,
243}
244
245#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
246pub struct UpdateOrganizationBody {
247 pub name: Option<String>,
248 pub description: Option<String>,
249 pub avatar_url: Option<String>,
250 #[serde(default)]
251 pub expected_version: i32,
252 #[serde(default)]
253 pub join_policy: Option<OrganizationJoinPolicy>,
254 #[serde(default)]
255 pub allow_join: Option<bool>,
256 #[serde(default)]
257 pub require_email_verified: Option<bool>,
258 #[serde(default)]
259 pub require_mfa: Option<bool>,
260 #[serde(default)]
261 pub welcome_message: Option<String>,
262 #[serde(default)]
263 pub member_must_enable_2fa: Option<bool>,
264 #[serde(default)]
265 pub allow_invite: Option<bool>,
266 #[serde(default)]
267 pub allow_join_level: Option<i16>,
268}
269
270#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
271pub struct AddMemberBody {
272 pub user_id: i64,
273 #[serde(default)]
274 pub role: Option<OrgMemberRole>,
275 #[serde(default)]
276 pub role_keys: Vec<String>,
277}
278
279#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
280pub struct SetMemberRoleBody {
281 pub role: OrgMemberRole,
282 #[serde(default)]
283 pub expected_version: i32,
284}
285
286#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
287pub struct BanMemberBody {
288 pub reason: Option<String>,
289}
290
291#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
292pub struct CheckPermissionBody {
293 pub permission: String,
294 #[serde(default)]
295 pub user_id: Option<i64>,
296}
297
298#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
299pub struct CreateRoleBody {
300 pub key: String,
301 pub name: String,
302 pub description: Option<String>,
303 #[serde(default)]
304 pub rank: i32,
305 #[serde(default)]
306 pub default_role: bool,
307 #[serde(default)]
308 pub self_assignable: bool,
309 #[serde(default)]
310 pub permissions: Vec<PermissionGrant>,
311}
312
313#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
314pub struct SetRolePermissionsBody {
315 #[serde(default)]
316 pub permissions: Vec<PermissionGrant>,
317}
318
319/// 反序列化 `Option<Option<T>>`:字段缺失 → `None`,显式 `null` → `Some(None)`,
320/// 有值 → `Some(Some(v))`。PATCH 接口用它区分「没传」与「清空」。
321fn double_option<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
322where
323 T: Deserialize<'de>,
324 D: Deserializer<'de>,
325{
326 Deserialize::deserialize(deserializer).map(Some)
327}
328
329/// 会籍更新:管理端可改别名、通知偏好、免打扰与封禁附加信息。
330/// 所有字段缺失表示不变,显式 null 表示清空(仅限 Option 字段)。
331#[derive(Clone, Debug, Default, Serialize, Deserialize, ToSchema)]
332pub struct UpdateMemberBody {
333 #[serde(default, deserialize_with = "double_option")]
334 pub alias_name: Option<Option<String>>,
335 #[serde(default, deserialize_with = "double_option")]
336 pub alias_avatar_url: Option<Option<String>>,
337 #[serde(default)]
338 pub message_notifications: Option<MessageNotifications>,
339 #[serde(default)]
340 pub muted: Option<bool>,
341 #[serde(default, deserialize_with = "double_option")]
342 pub mute_until: Option<Option<DateTime<Utc>>>,
343 #[serde(default)]
344 pub mobile_push: Option<bool>,
345 #[serde(default)]
346 pub suppress_everyone: Option<bool>,
347 #[serde(default)]
348 pub suppress_roles: Option<bool>,
349 #[serde(default)]
350 pub hide_muted_channels: Option<bool>,
351 #[serde(default)]
352 pub expected_version: i32,
353}
354
355/// 封禁更新:原因、到期时间与四个放行位。缺失表示不变,null 表示清空。
356#[derive(Clone, Debug, Default, Serialize, Deserialize, ToSchema)]
357pub struct UpdateBanBody {
358 #[serde(default, deserialize_with = "double_option")]
359 pub reason: Option<Option<String>>,
360 #[serde(default, deserialize_with = "double_option")]
361 pub expires_at: Option<Option<DateTime<Utc>>>,
362 #[serde(default)]
363 pub allow_agent_join: Option<bool>,
364 #[serde(default)]
365 pub allow_speak: Option<bool>,
366 #[serde(default)]
367 pub allow_push_repo: Option<bool>,
368 #[serde(default)]
369 pub allow_comment_any: Option<bool>,
370}
371
372pub const MAX_ALIAS_NAME_LEN: usize = 64;
373pub const MAX_ALIAS_AVATAR_URL_LEN: usize = 512;
374
375/// 校验并规范化别名:空白视为清空,超长返回 `Err(())`,由调用方统一翻译成 400。
376#[allow(clippy::result_unit_err)]
377pub fn normalize_alias(value: &str, _field: &str, max: usize) -> Result<Option<String>, ()> {
378 let trimmed = value.trim();
379 if trimmed.is_empty() {
380 return Ok(None);
381 }
382 if trimmed.len() > max {
383 return Err(());
384 }
385 Ok(Some(trimmed.to_owned()))
386}
387
388/// 内部使用的解析结果。`matched_by` 取值固定为 owner / admin / role / direct / default_deny,
389/// 只有 reason 为空时 allowed 才可能为 true。
390pub struct Resolution {
391 pub allowed: bool,
392 pub reason: &'static str,
393 pub matched_by: &'static str,
394 pub effective: Vec<String>,
395}
396
397#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
398pub struct JoinRuleSummary {
399 pub id: Uuid,
400 pub organization_id: Uuid,
401 pub role_id: Uuid,
402 pub trigger: String,
403 pub matcher: String,
404 pub enabled: bool,
405 pub created_at: DateTime<Utc>,
406 pub updated_at: DateTime<Utc>,
407}
408
409#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
410pub struct JoinRequestSummary {
411 pub id: Uuid,
412 pub organization_id: Uuid,
413 pub user_id: i64,
414 pub username: String,
415 pub displayname: Option<String>,
416 pub status: OrganizationJoinRequestStatus,
417 pub note: Option<String>,
418 pub decided_by: Option<i64>,
419 pub decided_at: Option<DateTime<Utc>>,
420 pub created_at: DateTime<Utc>,
421 pub updated_at: DateTime<Utc>,
422}
Added lib/auth/src/ssh_key/mod.rs lib/auth/src/ssh_key/mod.rs +4 −0 View lib/auth/src/ssh_key/mod.rs
@@ -0,0 +1,4 @@
1pub mod service;
2pub mod types;
3
4pub use types::*;
Added lib/auth/src/ssh_key/service.rs lib/auth/src/ssh_key/service.rs +294 −0 View lib/auth/src/ssh_key/service.rs
@@ -0,0 +1,294 @@
1use crate::events::{Labels, label};
2use crate::models::users::user_events::{self, UserEventType};
3use crate::models::users::user_ssh_keys::{self, PublicKeyType};
4use crate::models::users::users;
5use crate::session::Session;
6use base64::Engine;
7use base64::engine::general_purpose::STANDARD_NO_PAD;
8use chrono::Utc;
9use sea_orm::entity::Set;
10use sea_orm::sea_query::{Expr, ExprTrait};
11use sea_orm::{
12 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, SqlErr,
13 TransactionTrait,
14};
15use uuid::Uuid;
16
17use crate::AuthSvc;
18use crate::errors::{AuthError, AuthResult};
19use crate::share::crypto;
20use crate::share::types::ListQuery;
21use crate::ssh_key::types::{
22 MAX_SSH_KEY_DESCRIPTION_LEN, MAX_SSH_KEY_NAME_LEN, MAX_SSH_PUBLIC_KEY_LEN,
23 MIN_SSH_PUBLIC_KEY_LEN, SshKeyAddRequest, SshKeyAlgorithm, SshKeySummary,
24};
25
26pub(crate) fn parse_public_key(raw: &str) -> AuthResult<(PublicKeyType, String)> {
27 let key_line = raw.trim();
28 if key_line.len() < MIN_SSH_PUBLIC_KEY_LEN || key_line.len() > MAX_SSH_PUBLIC_KEY_LEN {
29 return Err(AuthError::SshKeyInvalid);
30 }
31
32 let mut parts = key_line.split_whitespace();
33 let declared_type = parts.next().ok_or(AuthError::SshKeyInvalid)?;
34 let blob_text = parts.next().ok_or(AuthError::SshKeyInvalid)?;
35 let algorithm = match declared_type {
36 "ssh-rsa" => PublicKeyType::RSA,
37 "ssh-ed25519" => PublicKeyType::ED25519,
38 "ecdsa-sha2-nistp256" => PublicKeyType::ECDSA,
39 _ => return Err(AuthError::SshKeyInvalid),
40 };
41
42 let blob = STANDARD_NO_PAD
43 .decode(blob_text.trim_end_matches('='))
44 .map_err(|_| AuthError::SshKeyInvalid)?;
45 if blob.len() < 8 {
46 return Err(AuthError::SshKeyInvalid);
47 }
48
49 let header_len = u32::from_be_bytes([blob[0], blob[1], blob[2], blob[3]]) as usize;
50 if header_len == 0 || 4 + header_len > blob.len() {
51 return Err(AuthError::SshKeyInvalid);
52 }
53 let header =
54 std::str::from_utf8(&blob[4..4 + header_len]).map_err(|_| AuthError::SshKeyInvalid)?;
55 if header != declared_type {
56 return Err(AuthError::SshKeyInvalid);
57 }
58
59 Ok((
60 algorithm,
61 format!("SHA256:{}", crypto::sha256_b64_standard(&blob)),
62 ))
63}
64
65fn summary(record: user_ssh_keys::Model) -> SshKeySummary {
66 SshKeySummary {
67 id: record.id,
68 name: record.name,
69 description: record.description,
70 algorithm: SshKeyAlgorithm::from(record.algorithm),
71 fingerprint: record.fingerprint,
72 public_key: record.public_key,
73 created_at: record.created_at,
74 expires_at: record.expires_at,
75 last_used_at: record.last_used_at,
76 revoked_at: record.revoked_at,
77 }
78}
79
80impl AuthSvc {
81 #[tracing::instrument(skip_all, fields(request_id = %request_id, client_ip = ?session.client_ip().ok().flatten()))]
82 pub async fn add_ssh_key(
83 &self,
84 session: Session,
85 request: SshKeyAddRequest,
86 user_agent: Option<String>,
87 ip_addr: Option<String>,
88 request_id: String,
89 ) -> AuthResult<SshKeySummary> {
90 let user_id = self
91 .require_session(&session, Some(request_id.as_str()))
92 .await?;
93 self.add_ssh_key_for_user(user_id, request, user_agent, ip_addr, request_id)
94 .await
95 }
96
97 /// 与 `add_ssh_key` 同逻辑,但调用方已按 RPC 密钥确认过用户身份。
98 pub async fn add_ssh_key_for_user(
99 &self,
100 user_id: i64,
101 request: SshKeyAddRequest,
102 user_agent: Option<String>,
103 ip_addr: Option<String>,
104 request_id: String,
105 ) -> AuthResult<SshKeySummary> {
106 let user = users::Entity::find_by_id(user_id)
107 .one(self.db().read())
108 .await?
109 .ok_or(AuthError::Unauthenticated)?;
110 if user.deleted_at.is_some() || !user.allow_use {
111 return Err(AuthError::Unauthenticated);
112 }
113
114 let name = request.name.trim().to_owned();
115 if name.is_empty() || name.chars().count() > MAX_SSH_KEY_NAME_LEN {
116 return Err(AuthError::SshKeyNameInvalid);
117 }
118 let description = request
119 .description
120 .as_deref()
121 .map(str::trim)
122 .filter(|text| !text.is_empty())
123 .map(str::to_owned);
124 if description
125 .as_ref()
126 .is_some_and(|text| text.chars().count() > MAX_SSH_KEY_DESCRIPTION_LEN)
127 {
128 return Err(AuthError::MalformedRequest);
129 }
130
131 let expiry = request.expires_at;
132 if expiry.is_some_and(|at| at <= Utc::now()) {
133 return Err(AuthError::InvalidExpiry);
134 }
135
136 let public_key = request.public_key.trim().to_owned();
137 let (algorithm, fingerprint) = parse_public_key(&public_key)?;
138
139 let inserted = user_ssh_keys::ActiveModel {
140 user_id: Set(user_id),
141 name: Set(name),
142 description: Set(description),
143 public_key: Set(public_key),
144 algorithm: Set(algorithm),
145 fingerprint: Set(fingerprint),
146 created_at: Set(Utc::now()),
147 expires_at: Set(expiry),
148 ..Default::default()
149 }
150 .insert(self.db().write())
151 .await
152 .map_err(|error| match error.sql_err() {
153 Some(SqlErr::UniqueConstraintViolation(_)) => AuthError::SshKeyTaken,
154 _ => AuthError::Database(crate::db::DbError::from(error)),
155 })?;
156
157 user_events::ActiveModel {
158 user_id: Set(user_id),
159 actor_user_id: Set(Some(user_id)),
160 event_type: Set(UserEventType::PublicKeyAdded),
161 occurred_at: Set(Utc::now()),
162 user_agent: Set(user_agent),
163 trace_id: Set(Some(request_id.clone())),
164 ip_address: Set(ip_addr),
165 details: Set(Some(serde_json::json!({
166 "ssh_key_id": inserted.id,
167 "name": inserted.name,
168 "fingerprint": inserted.fingerprint,
169 }))),
170 ..Default::default()
171 }
172 .insert(self.db().write())
173 .await?;
174
175 let response = summary(inserted);
176 self.emit_labeled(
177 label::AUTH_SSH_KEY_CREATE,
178 Labels::new()
179 .with(label::USER_ID, user_id)
180 .with(label::REQUEST_ID, &request_id),
181 &response,
182 Some(request_id.as_str()),
183 )
184 .await;
185 Ok(response)
186 }
187
188 #[tracing::instrument(skip_all, fields(request_id = %_request_id, client_ip = ?session.client_ip().ok().flatten()))]
189 pub async fn list_ssh_keys(
190 &self,
191 session: Session,
192 query: ListQuery,
193 _request_id: String,
194 ) -> AuthResult<Vec<SshKeySummary>> {
195 let user_id = self
196 .require_session(&session, Some(_request_id.as_str()))
197 .await?;
198 let records = user_ssh_keys::Entity::find()
199 .filter(user_ssh_keys::Column::UserId.eq(user_id))
200 .order_by_desc(user_ssh_keys::Column::CreatedAt)
201 .limit(query.limit())
202 .offset(query.offset())
203 .all(self.db().read())
204 .await?;
205
206 Ok(records.into_iter().map(summary).collect())
207 }
208
209 /// 吊销公钥与提高账号 `security_version` 在同一事务里完成:密钥泄露后,账号既有的访问令牌与
210 #[tracing::instrument(skip_all, fields(request_id = %request_id, client_ip = ?session.client_ip().ok().flatten()))]
211 pub async fn revoke_ssh_key(
212 &self,
213 session: Session,
214 key_id: Uuid,
215 user_agent: Option<String>,
216 ip_addr: Option<String>,
217 request_id: String,
218 ) -> AuthResult<()> {
219 let user_id = self
220 .require_session(&session, Some(request_id.as_str()))
221 .await?;
222 let record = user_ssh_keys::Entity::find_by_id(key_id)
223 .filter(user_ssh_keys::Column::UserId.eq(user_id))
224 .one(self.db().read())
225 .await?
226 .ok_or(AuthError::SshKeyNotFound)?;
227 if record.revoked_at.is_some() {
228 return Ok(());
229 }
230
231 let transaction = self.db().write().begin().await?;
232 let result: AuthResult<()> = async {
233 user_ssh_keys::Entity::update_many()
234 .col_expr(user_ssh_keys::Column::RevokedAt, Expr::value(Utc::now()))
235 .filter(user_ssh_keys::Column::Id.eq(record.id))
236 .exec(&transaction)
237 .await?;
238
239 users::Entity::update_many()
240 .col_expr(
241 users::Column::SecurityVersion,
242 Expr::col(users::Column::SecurityVersion).add(1),
243 )
244 .col_expr(users::Column::UpdatedAt, Expr::value(Utc::now()))
245 .filter(users::Column::Id.eq(user_id))
246 .exec(&transaction)
247 .await?;
248
249 user_events::ActiveModel {
250 user_id: Set(user_id),
251 actor_user_id: Set(Some(user_id)),
252 event_type: Set(UserEventType::PublicKeyRemoved),
253 occurred_at: Set(Utc::now()),
254 user_agent: Set(user_agent),
255 trace_id: Set(Some(request_id.clone())),
256 ip_address: Set(ip_addr),
257 details: Set(Some(serde_json::json!({
258 "ssh_key_id": record.id,
259 "name": record.name,
260 "fingerprint": record.fingerprint,
261 }))),
262 ..Default::default()
263 }
264 .insert(&transaction)
265 .await?;
266
267 Ok(())
268 }
269 .await;
270
271 match result {
272 Ok(()) => transaction.commit().await?,
273 Err(error) => {
274 let _ = transaction.rollback().await;
275 return Err(error);
276 }
277 }
278
279 self.emit_labeled(
280 label::AUTH_SSH_KEY_DELETE,
281 Labels::new()
282 .with(label::USER_ID, user_id)
283 .with(label::REQUEST_ID, &request_id),
284 serde_json::json!({
285 "user_id": user_id,
286 "ssh_key_id": record.id,
287 }),
288 Some(request_id.as_str()),
289 )
290 .await;
291
292 Ok(())
293 }
294}
Added lib/auth/src/ssh_key/types.rs lib/auth/src/ssh_key/types.rs +50 −0 View lib/auth/src/ssh_key/types.rs
@@ -0,0 +1,50 @@
1use crate::models::users::user_ssh_keys::PublicKeyType;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5use uuid::Uuid;
6
7pub const MAX_SSH_KEY_NAME_LEN: usize = 64;
8pub const MAX_SSH_KEY_DESCRIPTION_LEN: usize = 255;
9pub const MIN_SSH_PUBLIC_KEY_LEN: usize = 32;
10pub const MAX_SSH_PUBLIC_KEY_LEN: usize = 8192;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
13#[serde(rename_all = "snake_case")]
14pub enum SshKeyAlgorithm {
15 Rsa,
16 Ed25519,
17 Ecdsa,
18}
19
20impl From<PublicKeyType> for SshKeyAlgorithm {
21 fn from(value: PublicKeyType) -> Self {
22 match value {
23 PublicKeyType::RSA => Self::Rsa,
24 PublicKeyType::ED25519 => Self::Ed25519,
25 PublicKeyType::ECDSA => Self::Ecdsa,
26 }
27 }
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
31pub struct SshKeyAddRequest {
32 pub name: String,
33 pub description: Option<String>,
34 pub public_key: String,
35 pub expires_at: Option<DateTime<Utc>>,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
39pub struct SshKeySummary {
40 pub id: Uuid,
41 pub name: String,
42 pub description: Option<String>,
43 pub algorithm: SshKeyAlgorithm,
44 pub fingerprint: String,
45 pub public_key: String,
46 pub created_at: DateTime<Utc>,
47 pub expires_at: Option<DateTime<Utc>>,
48 pub last_used_at: Option<DateTime<Utc>>,
49 pub revoked_at: Option<DateTime<Utc>>,
50}
Renamed lib/auth/src/auth_api_adapter.rs → lib/auth/src/auth_api_impl.rs lib/auth/src/auth_api_impl.rs +186 −241 View lib/auth/src/auth_api_impl.rs
@@ -4,21 +4,10 @@
44use crate::service::AuthService;
55use export::{AuthApi, KenAuthError};
66
7#[derive(Clone)]
8pub struct AuthApiAdapter {
9 pub inner: AuthService,
10}
11
12impl AuthApiAdapter {
13 pub fn new(inner: AuthService) -> Self {
14 Self { inner }
15 }
16}
17
187#[async_trait]
19impl AuthApi for AuthApiAdapter {
8impl AuthApi for AuthService {
209 async fn check_health(&self) -> Result<(), KenAuthError> {
21 self.inner.check_health().await
10 self.check_health().await
2211 }
2312
2413 async fn change_password(
@@ -29,22 +18,21 @@
2918 keep_session_id: &str,
3019 client_ip: &str,
3120 ) -> Result<proto::v1::ChangePasswordResponse, KenAuthError> {
32 self.inner
33 .change_password(
34 user_id,
35 old_password_hash,
36 new_password_hash,
37 keep_session_id,
38 client_ip,
39 )
40 .await
21 self.change_password(
22 user_id,
23 old_password_hash,
24 new_password_hash,
25 keep_session_id,
26 client_ip,
27 )
28 .await
4129 }
4230
4331 async fn get_mfa_status(
4432 &self,
4533 user_id: i64,
4634 ) -> Result<proto::v1::GetMfaStatusResponse, KenAuthError> {
47 self.inner.get_mfa_status(user_id).await
35 self.get_mfa_status(user_id).await
4836 }
4937
5038 async fn configure_totp(
@@ -53,7 +41,7 @@
5341 name: &str,
5442 client_ip: &str,
5543 ) -> Result<proto::v1::ConfigureTotpResponse, KenAuthError> {
56 self.inner.configure_totp(user_id, name, client_ip).await
44 self.configure_totp(user_id, name, client_ip).await
5745 }
5846
5947 async fn enable_totp(
@@ -63,8 +51,7 @@
6351 password_hash: &str,
6452 client_ip: &str,
6553 ) -> Result<proto::v1::EnableTotpResponse, KenAuthError> {
66 self.inner
67 .enable_totp(user_id, code, password_hash, client_ip)
54 self.enable_totp(user_id, code, password_hash, client_ip)
6855 .await
6956 }
7057
@@ -74,7 +61,7 @@
7461 code: &str,
7562 client_ip: &str,
7663 ) -> Result<proto::v1::DisableTotpResponse, KenAuthError> {
77 self.inner.disable_totp(user_id, code, client_ip).await
64 self.disable_totp(user_id, code, client_ip).await
7865 }
7966
8067 async fn regenerate_recovery_codes(
@@ -83,8 +70,7 @@
8370 password_hash: &str,
8471 client_ip: &str,
8572 ) -> Result<proto::v1::RegenerateRecoveryCodesResponse, KenAuthError> {
86 self.inner
87 .regenerate_recovery_codes(user_id, password_hash, client_ip)
73 self.regenerate_recovery_codes(user_id, password_hash, client_ip)
8874 .await
8975 }
9076
@@ -94,8 +80,7 @@
9480 include_revoked: bool,
9581 page: proto::common::v1::PageRequest,
9682 ) -> Result<proto::v1::ListUserSshKeysResponse, KenAuthError> {
97 self.inner
98 .list_user_ssh_keys(user_id, include_revoked, page)
83 self.list_user_ssh_keys(user_id, include_revoked, page)
9984 .await
10085 }
10186
@@ -107,8 +92,7 @@
10792 description: &str,
10893 client_ip: &str,
10994 ) -> Result<proto::v1::AddSshKeyResponse, KenAuthError> {
110 self.inner
111 .add_ssh_key(user_id, name, public_key, description, client_ip)
95 self.add_ssh_key(user_id, name, public_key, description, client_ip)
11296 .await
11397 }
11498
@@ -119,8 +103,7 @@
119103 reason: &str,
120104 bump_security_version: bool,
121105 ) -> Result<proto::v1::RevokeSshKeyResponse, KenAuthError> {
122 self.inner
123 .revoke_ssh_key(user_id, key_id, reason, bump_security_version)
106 self.revoke_ssh_key(user_id, key_id, reason, bump_security_version)
124107 .await
125108 }
126109
@@ -131,8 +114,7 @@
131114 include_expired: bool,
132115 page: proto::common::v1::PageRequest,
133116 ) -> Result<proto::v1::ListUserSessionsResponse, KenAuthError> {
134 self.inner
135 .list_user_sessions(user_id, include_revoked, include_expired, page)
117 self.list_user_sessions(user_id, include_revoked, include_expired, page)
136118 .await
137119 }
138120
@@ -143,8 +125,7 @@
143125 reason: &str,
144126 bump_security_version: bool,
145127 ) -> Result<proto::v1::RevokeUserSessionsResponse, KenAuthError> {
146 self.inner
147 .revoke_user_sessions(user_id, except_session_id, reason, bump_security_version)
128 self.revoke_user_sessions(user_id, except_session_id, reason, bump_security_version)
148129 .await
149130 }
150131
@@ -154,47 +135,47 @@
154135 version: i32,
155136 session_id: &str,
156137 ) -> Result<proto::internal::v1::AuthorityAccess, KenAuthError> {
157 self.inner.get_session(user_id, version, session_id).await
138 self.get_session(user_id, version, session_id).await
158139 }
159140
160141 async fn delete_account(&self, user_id: i64, reason: &str) -> Result<(), KenAuthError> {
161 self.inner.delete_account(user_id, reason).await
142 self.delete_account(user_id, reason).await
162143 }
163144
164145 async fn cancel_account_deletion(&self, user_id: i64) -> Result<(), KenAuthError> {
165 self.inner.cancel_account_deletion(user_id).await
146 self.cancel_account_deletion(user_id).await
166147 }
167148
168149 async fn purge_account(&self, user_id: i64) -> Result<(), KenAuthError> {
169 self.inner.purge_account(user_id).await
150 self.purge_account(user_id).await
170151 }
171152
172153 async fn batch_get_authority_users(
173154 &self,
174155 user_ids: Vec<i64>,
175156 ) -> Result<Vec<proto::internal::v1::AuthorityUser>, KenAuthError> {
176 self.inner.batch_get_authority_users(user_ids).await
157 self.batch_get_authority_users(user_ids).await
177158 }
178159
179160 async fn get_user_status(
180161 &self,
181162 user_id: i64,
182163 ) -> Result<proto::common::v1::UserStatus, KenAuthError> {
183 self.inner.get_user_status(user_id).await
164 self.get_user_status(user_id).await
184165 }
185166
186167 async fn get_user_public_by_ref(
187168 &self,
188169 key: proto::common::v1::user_ref::Key,
189170 ) -> Result<proto::common::v1::UserPublic, KenAuthError> {
190 self.inner.get_user_public_by_ref(key).await
171 self.get_user_public_by_ref(key).await
191172 }
192173
193174 async fn get_authority_user(
194175 &self,
195176 user_id: i64,
196177 ) -> Result<proto::internal::v1::AuthorityUser, KenAuthError> {
197 self.inner.get_authority_user(user_id).await
178 self.get_authority_user(user_id).await
198179 }
199180
200181 async fn update_authority_user(
@@ -206,30 +187,29 @@
206187 public_show_email: Option<bool>,
207188 version: i32,
208189 ) -> Result<proto::internal::v1::AuthorityUser, KenAuthError> {
209 self.inner
210 .update_authority_user(
211 user_id,
212 username,
213 displayname,
214 clear_displayname,
215 public_show_email,
216 version,
217 )
218 .await
190 self.update_authority_user(
191 user_id,
192 username,
193 displayname,
194 clear_displayname,
195 public_show_email,
196 version,
197 )
198 .await
219199 }
220200
221201 async fn batch_get_user_public(
222202 &self,
223203 user_ids: Vec<i64>,
224204 ) -> Result<Vec<proto::common::v1::UserPublic>, KenAuthError> {
225 self.inner.batch_get_user_public(user_ids).await
205 self.batch_get_user_public(user_ids).await
226206 }
227207
228208 async fn get_user_public(
229209 &self,
230210 user_id: i64,
231211 ) -> Result<proto::common::v1::UserPublic, KenAuthError> {
232 self.inner.get_user_public(user_id).await
212 self.get_user_public(user_id).await
233213 }
234214
235215 async fn revoke_session(
@@ -238,14 +218,14 @@
238218 session_id: &str,
239219 reason: &str,
240220 ) -> Result<(), KenAuthError> {
241 self.inner.revoke_session(user_id, session_id, reason).await
221 self.revoke_session(user_id, session_id, reason).await
242222 }
243223
244224 async fn lookup_ssh_key(
245225 &self,
246226 public_key: &str,
247227 ) -> Result<Option<proto::common::v1::Principal>, KenAuthError> {
248 self.inner.lookup_ssh_key(public_key).await
228 self.lookup_ssh_key(public_key).await
249229 }
250230
251231 async fn ensure_bot_user(
@@ -254,9 +234,7 @@
254234 displayname: &str,
255235 system: bool,
256236 ) -> Result<proto::common::v1::UserPublic, KenAuthError> {
257 self.inner
258 .ensure_bot_user(username, displayname, system)
259 .await
237 self.ensure_bot_user(username, displayname, system).await
260238 }
261239
262240 async fn ensure_bot_member(
@@ -266,8 +244,7 @@
266244 role_keys: Vec<String>,
267245 role_ids: Vec<String>,
268246 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
269 self.inner
270 .ensure_bot_member(organization_id, user_id, role_keys, role_ids)
247 self.ensure_bot_member(organization_id, user_id, role_keys, role_ids)
271248 .await
272249 }
273250
@@ -277,8 +254,7 @@
277254 organization_id: &str,
278255 slug: &str,
279256 ) -> Result<proto::v1::GetOrganizationResponse, KenAuthError> {
280 self.inner
281 .get_organization(actor_user_id, organization_id, slug)
257 self.get_organization(actor_user_id, organization_id, slug)
282258 .await
283259 }
284260
@@ -288,8 +264,7 @@
288264 organization_id: &str,
289265 user_id: i64,
290266 ) -> Result<Option<proto::v1::OrganizationMemberInfo>, KenAuthError> {
291 self.inner
292 .get_member(actor_user_id, organization_id, user_id)
267 self.get_member(actor_user_id, organization_id, user_id)
293268 .await
294269 }
295270
@@ -301,15 +276,14 @@
301276 include_banned: bool,
302277 page: proto::common::v1::PageRequest,
303278 ) -> Result<proto::v1::ListMembersResponse, KenAuthError> {
304 self.inner
305 .list_members(
306 actor_user_id,
307 organization_id,
308 include_inactive,
309 include_banned,
310 page,
311 )
312 .await
279 self.list_members(
280 actor_user_id,
281 organization_id,
282 include_inactive,
283 include_banned,
284 page,
285 )
286 .await
313287 }
314288
315289 async fn list_organizations(
@@ -319,8 +293,7 @@
319293 include_archived: bool,
320294 page: proto::common::v1::PageRequest,
321295 ) -> Result<proto::v1::ListOrganizationsResponse, KenAuthError> {
322 self.inner
323 .list_organizations(actor_user_id, user_id, include_archived, page)
296 self.list_organizations(actor_user_id, user_id, include_archived, page)
324297 .await
325298 }
326299
@@ -329,7 +302,7 @@
329302 query: &str,
330303 page: proto::common::v1::PageRequest,
331304 ) -> Result<proto::v1::ListPublicOrganizationsResponse, KenAuthError> {
332 self.inner.list_public_organizations(query, page).await
305 self.list_public_organizations(query, page).await
333306 }
334307
335308 async fn check_permissions(
@@ -339,8 +312,7 @@
339312 user_id: i64,
340313 permissions: Vec<String>,
341314 ) -> Result<proto::v1::CheckPermissionsResponse, KenAuthError> {
342 self.inner
343 .check_permissions(actor_user_id, organization_id, user_id, permissions)
315 self.check_permissions(actor_user_id, organization_id, user_id, permissions)
344316 .await
345317 }
346318
@@ -360,23 +332,22 @@
360332 allow_invite: Option<bool>,
361333 allow_join_level: Option<i32>,
362334 ) -> Result<proto::v1::CreateOrganizationResponse, KenAuthError> {
363 self.inner
364 .create_organization(
365 actor_user_id,
366 slug,
367 name,
368 description,
369 avatar_url,
370 join_policy,
371 allow_join,
372 require_email_verified,
373 require_mfa,
374 welcome_message,
375 member_must_enable_2fa,
376 allow_invite,
377 allow_join_level,
378 )
379 .await
335 self.create_organization(
336 actor_user_id,
337 slug,
338 name,
339 description,
340 avatar_url,
341 join_policy,
342 allow_join,
343 require_email_verified,
344 require_mfa,
345 welcome_message,
346 member_must_enable_2fa,
347 allow_invite,
348 allow_join_level,
349 )
350 .await
380351 }
381352
382353 async fn update_organization(
@@ -396,24 +367,23 @@
396367 allow_invite: Option<bool>,
397368 allow_join_level: Option<i32>,
398369 ) -> Result<proto::v1::OrganizationInfo, KenAuthError> {
399 self.inner
400 .update_organization(
401 actor_user_id,
402 organization_id,
403 name,
404 description,
405 avatar_url,
406 expected_version,
407 join_policy,
408 allow_join,
409 require_email_verified,
410 require_mfa,
411 welcome_message,
412 member_must_enable_2fa,
413 allow_invite,
414 allow_join_level,
415 )
416 .await
370 self.update_organization(
371 actor_user_id,
372 organization_id,
373 name,
374 description,
375 avatar_url,
376 expected_version,
377 join_policy,
378 allow_join,
379 require_email_verified,
380 require_mfa,
381 welcome_message,
382 member_must_enable_2fa,
383 allow_invite,
384 allow_join_level,
385 )
386 .await
417387 }
418388
419389 async fn archive_organization(
@@ -422,8 +392,7 @@
422392 organization_id: &str,
423393 reason: &str,
424394 ) -> Result<(), KenAuthError> {
425 self.inner
426 .archive_organization(actor_user_id, organization_id, reason)
395 self.archive_organization(actor_user_id, organization_id, reason)
427396 .await
428397 }
429398
@@ -432,8 +401,7 @@
432401 actor_user_id: i64,
433402 organization_id: &str,
434403 ) -> Result<(), KenAuthError> {
435 self.inner
436 .restore_organization(actor_user_id, organization_id)
404 self.restore_organization(actor_user_id, organization_id)
437405 .await
438406 }
439407
@@ -444,8 +412,7 @@
444412 force: bool,
445413 reason: &str,
446414 ) -> Result<(), KenAuthError> {
447 self.inner
448 .delete_organization(actor_user_id, organization_id, force, reason)
415 self.delete_organization(actor_user_id, organization_id, force, reason)
449416 .await
450417 }
451418
@@ -457,8 +424,7 @@
457424 role: i32,
458425 role_keys: Vec<String>,
459426 ) -> Result<(proto::v1::OrganizationMemberInfo, bool), KenAuthError> {
460 self.inner
461 .add_member(actor_user_id, organization_id, user_id, role, role_keys)
427 self.add_member(actor_user_id, organization_id, user_id, role, role_keys)
462428 .await
463429 }
464430
@@ -469,8 +435,7 @@
469435 user_id: i64,
470436 reason: &str,
471437 ) -> Result<bool, KenAuthError> {
472 self.inner
473 .remove_member(actor_user_id, organization_id, user_id, reason)
438 self.remove_member(actor_user_id, organization_id, user_id, reason)
474439 .await
475440 }
476441
@@ -479,8 +444,7 @@
479444 actor_user_id: i64,
480445 organization_id: &str,
481446 ) -> Result<bool, KenAuthError> {
482 self.inner
483 .leave_organization(actor_user_id, organization_id)
447 self.leave_organization(actor_user_id, organization_id)
484448 .await
485449 }
486450
@@ -499,22 +463,21 @@
499463 suppress_roles: Option<bool>,
500464 hide_muted_channels: Option<bool>,
501465 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
502 self.inner
503 .update_member(
504 actor_user_id,
505 organization_id,
506 user_id,
507 alias_name,
508 alias_avatar_url,
509 message_notifications,
510 muted,
511 mute_until,
512 mobile_push,
513 suppress_everyone,
514 suppress_roles,
515 hide_muted_channels,
516 )
517 .await
466 self.update_member(
467 actor_user_id,
468 organization_id,
469 user_id,
470 alias_name,
471 alias_avatar_url,
472 message_notifications,
473 muted,
474 mute_until,
475 mobile_push,
476 suppress_everyone,
477 suppress_roles,
478 hide_muted_channels,
479 )
480 .await
518481 }
519482
520483 async fn ban_member(
@@ -524,8 +487,7 @@
524487 user_id: i64,
525488 reason: &str,
526489 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
527 self.inner
528 .ban_member(actor_user_id, organization_id, user_id, reason)
490 self.ban_member(actor_user_id, organization_id, user_id, reason)
529491 .await
530492 }
531493
@@ -535,8 +497,7 @@
535497 organization_id: &str,
536498 user_id: i64,
537499 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
538 self.inner
539 .unban_member(actor_user_id, organization_id, user_id)
500 self.unban_member(actor_user_id, organization_id, user_id)
540501 .await
541502 }
542503
@@ -551,18 +512,17 @@
551512 allow_push_repo: Option<bool>,
552513 allow_comment_any: Option<bool>,
553514 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
554 self.inner
555 .update_ban(
556 actor_user_id,
557 organization_id,
558 user_id,
559 reason,
560 expires_at,
561 allow_speak,
562 allow_push_repo,
563 allow_comment_any,
564 )
565 .await
515 self.update_ban(
516 actor_user_id,
517 organization_id,
518 user_id,
519 reason,
520 expires_at,
521 allow_speak,
522 allow_push_repo,
523 allow_comment_any,
524 )
525 .await
566526 }
567527
568528 async fn create_role(
@@ -576,18 +536,17 @@
576536 self_assignable: bool,
577537 permissions: Vec<String>,
578538 ) -> Result<proto::v1::OrganizationRoleInfo, KenAuthError> {
579 self.inner
580 .create_role(
581 actor_user_id,
582 organization_id,
583 key,
584 name,
585 description,
586 rank,
587 self_assignable,
588 permissions,
589 )
590 .await
539 self.create_role(
540 actor_user_id,
541 organization_id,
542 key,
543 name,
544 description,
545 rank,
546 self_assignable,
547 permissions,
548 )
549 .await
591550 }
592551
593552 async fn update_role(
@@ -601,18 +560,17 @@
601560 self_assignable: Option<bool>,
602561 expected_version: i32,
603562 ) -> Result<proto::v1::OrganizationRoleInfo, KenAuthError> {
604 self.inner
605 .update_role(
606 actor_user_id,
607 organization_id,
608 role_id,
609 name,
610 description,
611 rank,
612 self_assignable,
613 expected_version,
614 )
615 .await
563 self.update_role(
564 actor_user_id,
565 organization_id,
566 role_id,
567 name,
568 description,
569 rank,
570 self_assignable,
571 expected_version,
572 )
573 .await
616574 }
617575
618576 async fn set_role_permissions(
@@ -622,8 +580,7 @@
622580 role_id: &str,
623581 permissions: Vec<String>,
624582 ) -> Result<proto::v1::OrganizationRoleInfo, KenAuthError> {
625 self.inner
626 .set_role_permissions(actor_user_id, organization_id, role_id, permissions)
583 self.set_role_permissions(actor_user_id, organization_id, role_id, permissions)
627584 .await
628585 }
629586
@@ -633,8 +590,7 @@
633590 organization_id: &str,
634591 role_id: &str,
635592 ) -> Result<bool, KenAuthError> {
636 self.inner
637 .delete_role(actor_user_id, organization_id, role_id)
593 self.delete_role(actor_user_id, organization_id, role_id)
638594 .await
639595 }
640596
@@ -645,8 +601,7 @@
645601 user_id: i64,
646602 role_id: &str,
647603 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
648 self.inner
649 .assign_role(actor_user_id, organization_id, user_id, role_id)
604 self.assign_role(actor_user_id, organization_id, user_id, role_id)
650605 .await
651606 }
652607
@@ -657,8 +612,7 @@
657612 user_id: i64,
658613 role_id: &str,
659614 ) -> Result<proto::v1::OrganizationMemberInfo, KenAuthError> {
660 self.inner
661 .unassign_role(actor_user_id, organization_id, user_id, role_id)
615 self.unassign_role(actor_user_id, organization_id, user_id, role_id)
662616 .await
663617 }
664618
@@ -673,18 +627,17 @@
673627 target_user_id: i64,
674628 email: &str,
675629 ) -> Result<(proto::v1::OrganizationInviteInfo, String), KenAuthError> {
676 self.inner
677 .create_invite(
678 actor_user_id,
679 organization_id,
680 method,
681 max_uses,
682 expires_at,
683 role_ids,
684 target_user_id,
685 email,
686 )
687 .await
630 self.create_invite(
631 actor_user_id,
632 organization_id,
633 method,
634 max_uses,
635 expires_at,
636 role_ids,
637 target_user_id,
638 email,
639 )
640 .await
688641 }
689642
690643 async fn revoke_invite(
@@ -693,8 +646,7 @@
693646 organization_id: &str,
694647 invite_id: &str,
695648 ) -> Result<bool, KenAuthError> {
696 self.inner
697 .revoke_invite(actor_user_id, organization_id, invite_id)
649 self.revoke_invite(actor_user_id, organization_id, invite_id)
698650 .await
699651 }
700652
@@ -703,7 +655,7 @@
703655 actor_user_id: i64,
704656 token: &str,
705657 ) -> Result<proto::v1::RedeemInviteResponse, KenAuthError> {
706 self.inner.redeem_invite(actor_user_id, token).await
658 self.redeem_invite(actor_user_id, token).await
707659 }
708660
709661 async fn create_join_rule(
@@ -715,16 +667,15 @@
715667 matcher: &str,
716668 enabled: bool,
717669 ) -> Result<proto::v1::JoinRuleInfo, KenAuthError> {
718 self.inner
719 .create_join_rule(
720 actor_user_id,
721 organization_id,
722 role_id,
723 trigger,
724 matcher,
725 enabled,
726 )
727 .await
670 self.create_join_rule(
671 actor_user_id,
672 organization_id,
673 role_id,
674 trigger,
675 matcher,
676 enabled,
677 )
678 .await
728679 }
729680
730681 async fn update_join_rule(
@@ -737,17 +688,16 @@
737688 matcher: Option<String>,
738689 enabled: Option<bool>,
739690 ) -> Result<proto::v1::JoinRuleInfo, KenAuthError> {
740 self.inner
741 .update_join_rule(
742 actor_user_id,
743 organization_id,
744 rule_id,
745 role_id,
746 trigger,
747 matcher,
748 enabled,
749 )
750 .await
691 self.update_join_rule(
692 actor_user_id,
693 organization_id,
694 rule_id,
695 role_id,
696 trigger,
697 matcher,
698 enabled,
699 )
700 .await
751701 }
752702
753703 async fn delete_join_rule(
@@ -756,8 +706,7 @@
756706 organization_id: &str,
757707 rule_id: &str,
758708 ) -> Result<bool, KenAuthError> {
759 self.inner
760 .delete_join_rule(actor_user_id, organization_id, rule_id)
709 self.delete_join_rule(actor_user_id, organization_id, rule_id)
761710 .await
762711 }
763712
@@ -767,8 +716,7 @@
767716 organization_id: &str,
768717 note: &str,
769718 ) -> Result<proto::v1::JoinRequestInfo, KenAuthError> {
770 self.inner
771 .create_join_request(actor_user_id, organization_id, note)
719 self.create_join_request(actor_user_id, organization_id, note)
772720 .await
773721 }
774722
@@ -778,8 +726,7 @@
778726 organization_id: &str,
779727 request_id: &str,
780728 ) -> Result<proto::v1::JoinRequestInfo, KenAuthError> {
781 self.inner
782 .withdraw_join_request(actor_user_id, organization_id, request_id)
729 self.withdraw_join_request(actor_user_id, organization_id, request_id)
783730 .await
784731 }
785732
@@ -795,8 +742,7 @@
795742 ),
796743 KenAuthError,
797744 > {
798 self.inner
799 .approve_join_request(actor_user_id, organization_id, request_id)
745 self.approve_join_request(actor_user_id, organization_id, request_id)
800746 .await
801747 }
802748
@@ -807,8 +753,7 @@
807753 request_id: &str,
808754 note: &str,
809755 ) -> Result<proto::v1::JoinRequestInfo, KenAuthError> {
810 self.inner
811 .reject_join_request(actor_user_id, organization_id, request_id, note)
756 self.reject_join_request(actor_user_id, organization_id, request_id, note)
812757 .await
813758 }
814759}
Modified lib/auth/src/authority.rs lib/auth/src/authority.rs +1 −1 View lib/auth/src/authority.rs
@@ -212,7 +212,7 @@
212212 .ok_or(AuthError::VersionMismatch {
213213 current: user.version,
214214 })?;
215 crate::handlers::identity::me::service::invalidate_me_cache(self.cache(), user_id).await;
215 crate::me::service::invalidate_me_cache(self.cache(), user_id).await;
216216 Ok(updated)
217217 }
218218}
Modified lib/auth/src/lib.rs lib/auth/src/lib.rs +4 −2 View lib/auth/src/lib.rs
@@ -5,27 +5,29 @@
55pub mod account_lifecycle;
66pub mod api_error;
77pub mod assets;
8pub mod auth_api_adapter;
98pub mod auth_api_error;
9pub mod auth_api_impl;
1010pub mod auth_session;
1111pub mod auth_svc;
1212pub mod authority;
1313pub mod errors;
1414pub mod handlers;
1515pub mod internal;
16pub mod me;
1617pub mod migrate;
1718pub mod openapi;
1819pub mod openapi_account;
20pub mod organization;
1921pub mod pow;
2022pub mod routes;
2123pub mod routes_account;
2224pub mod service;
2325pub mod services;
2426pub mod share;
27pub mod ssh_key;
2528pub mod templates;
2629
2730pub use api_error::{ApiError, AuthBusinessError};
28pub use auth_api_adapter::AuthApiAdapter;
2931pub use auth_api_error::KenAuthError;
3032pub use auth_svc::AuthSvc;
3133pub use errors::{AuthError, AuthResult};
Modified lib/auth/src/openapi.rs lib/auth/src/openapi.rs +14 −14 View lib/auth/src/openapi.rs
@@ -62,20 +62,20 @@
6262 crate::handlers::identity::organization::handler::redeem_invite,
6363 ),
6464 components(schemas(
65 crate::handlers::identity::organization::types::OrganizationSummary,
66 crate::handlers::identity::organization::types::MemberSummary,
67 crate::handlers::identity::organization::types::RoleSummary,
68 crate::handlers::identity::organization::types::InviteSummary,
69 crate::handlers::identity::organization::types::PermissionGrant,
70 crate::handlers::identity::organization::types::PermissionCheck,
71 crate::handlers::identity::organization::types::CreateOrganizationBody,
72 crate::handlers::identity::organization::types::UpdateOrganizationBody,
73 crate::handlers::identity::organization::types::AddMemberBody,
74 crate::handlers::identity::organization::types::SetMemberRoleBody,
75 crate::handlers::identity::organization::types::BanMemberBody,
76 crate::handlers::identity::organization::types::CheckPermissionBody,
77 crate::handlers::identity::organization::types::CreateRoleBody,
78 crate::handlers::identity::organization::types::SetRolePermissionsBody,
65 crate::organization::types::OrganizationSummary,
66 crate::organization::types::MemberSummary,
67 crate::organization::types::RoleSummary,
68 crate::organization::types::InviteSummary,
69 crate::organization::types::PermissionGrant,
70 crate::organization::types::PermissionCheck,
71 crate::organization::types::CreateOrganizationBody,
72 crate::organization::types::UpdateOrganizationBody,
73 crate::organization::types::AddMemberBody,
74 crate::organization::types::SetMemberRoleBody,
75 crate::organization::types::BanMemberBody,
76 crate::organization::types::CheckPermissionBody,
77 crate::organization::types::CreateRoleBody,
78 crate::organization::types::SetRolePermissionsBody,
7979 crate::handlers::identity::organization::handler::OrgPath,
8080 crate::handlers::identity::organization::handler::OrgMemberPath,
8181 crate::handlers::identity::organization::handler::OrgRolePath,