feat(auth): expose the internal identity rpc
- give `AuthService` the session store and add `resolve_internal_session` (session cookie -> principal, with the usual security-version check) and `attest_internal_identity` (authoritative re-check for envelope identities) - register `internal.v1.IdentityService` and map auth failures onto tonic status codes; keep the RPC layer limited to proto conversion
ZhenYi committed
Added lib/auth/src/rpc/mod.rs lib/auth/src/rpc/mod.rs +78 −0 View lib/auth/src/rpc/mod.rs
@@ -0,0 +1,78 @@
1//! 内部 RPC 的 auth 域:协议转换只在这一层,业务逻辑在 `AuthService`。
2
3use internal::Gate;
4use internal::server::InternalRouter;
5use proto::internal::v1::identity_service_server::{
6 IdentityService as IdentityServiceTrait, IdentityServiceServer,
7};
8use proto::internal::v1::{
9 AttestIdentityRequest, AttestIdentityResponse, CredentialKind, ResolveIdentityRequest,
10 ResolveIdentityResponse,
11};
12use tonic::{Request, Response, Status};
13
14use crate::errors::AuthError;
15use crate::services::AuthService;
16
17pub fn register(router: InternalRouter, service: &AuthService, gate: Gate) -> InternalRouter {
18 router.add_service(IdentityServiceServer::with_interceptor(
19 IdentityRpc {
20 service: service.clone(),
21 },
22 gate,
23 ))
24}
25
26#[derive(Clone)]
27struct IdentityRpc {
28 service: AuthService,
29}
30
31#[tonic::async_trait]
32impl IdentityServiceTrait for IdentityRpc {
33 async fn resolve(
34 &self,
35 request: Request<ResolveIdentityRequest>,
36 ) -> Result<Response<ResolveIdentityResponse>, Status> {
37 let meta = internal::interceptor::meta(&request)?;
38 let request = request.into_inner();
39 if request.kind() != CredentialKind::Session {
40 return Err(Status::invalid_argument("unsupported credential kind"));
41 }
42 let principal = self
43 .service
44 .resolve_internal_session(&request.credential, &meta.request_id)
45 .await
46 .map_err(status_of)?;
47 Ok(Response::new(ResolveIdentityResponse {
48 principal: Some(principal),
49 }))
50 }
51
52 async fn attest(
53 &self,
54 request: Request<AttestIdentityRequest>,
55 ) -> Result<Response<AttestIdentityResponse>, Status> {
56 let meta = internal::interceptor::meta(&request)?;
57 let request = request.into_inner();
58 let principal = self
59 .service
60 .attest_internal_identity(request.user_id, request.security_version, &meta.request_id)
61 .await
62 .map_err(status_of)?;
63 Ok(Response::new(AttestIdentityResponse {
64 active: true,
65 principal: Some(principal),
66 }))
67 }
68}
69
70fn status_of(error: AuthError) -> Status {
71 match error {
72 AuthError::Unauthenticated => Status::unauthenticated("identity was rejected"),
73 other => {
74 tracing::warn!(%other, "internal identity rpc failed");
75 Status::internal("identity lookup failed")
76 }
77 }
78}
Added lib/auth/src/services/internal_identity.rs lib/auth/src/services/internal_identity.rs +82 −0 View lib/auth/src/services/internal_identity.rs
@@ -0,0 +1,82 @@
1use crate::errors::{AuthError, AuthResult};
2use crate::services::AuthService;
3use crate::session::{Session, SessionKey, SessionStore};
4
5impl AuthService {
6 /// 把 KenApi 会话键(session cookie 的值)解析成内部 RPC 使用的主体。
7 pub async fn resolve_internal_session(
8 &self,
9 credential: &str,
10 request_id: &str,
11 ) -> AuthResult<proto::internal::v1::SessionPrincipal> {
12 let key =
13 SessionKey::try_from(credential.to_owned()).map_err(|_| AuthError::Unauthenticated)?;
14 let Some((state, _ttl)) = self.sessions.load(&key).await? else {
15 return Err(AuthError::Unauthenticated);
16 };
17 // Session 内部是 Rc<RefCell<...>>(非 Send):先同步取完字段再 await。
18 let (user_id, security_version, auth_session_id) = {
19 let session = Session::from_state(state);
20 (
21 session.uid().ok_or(AuthError::Unauthenticated)?,
22 session
23 .security_version()?
24 .ok_or(AuthError::Unauthenticated)?,
25 session.session_id().ok_or(AuthError::Unauthenticated)?,
26 )
27 };
28 let (user, mfa_verified) = self
29 .accounts
30 .check_session(
31 user_id,
32 security_version,
33 &auth_session_id,
34 Some(request_id),
35 )
36 .await?;
37 Ok(principal_of(
38 &user,
39 security_version,
40 auth_session_id,
41 mfa_verified,
42 ))
43 }
44
45 /// 信封身份的权威复核:用户仍有效且安全版本一致。
46 pub async fn attest_internal_identity(
47 &self,
48 user_id: i64,
49 security_version: i64,
50 _request_id: &str,
51 ) -> AuthResult<proto::internal::v1::SessionPrincipal> {
52 if user_id <= 0 || security_version <= 0 {
53 return Err(AuthError::Unauthenticated);
54 }
55 let user = self.accounts.user(user_id).await?;
56 if user.security_version != security_version as i32 {
57 return Err(AuthError::Unauthenticated);
58 }
59 Ok(principal_of(
60 &user,
61 user.security_version,
62 String::new(),
63 false,
64 ))
65 }
66}
67
68fn principal_of(
69 user: &crate::models::users::users::Model,
70 security_version: i32,
71 session_id: String,
72 mfa_verified: bool,
73) -> proto::internal::v1::SessionPrincipal {
74 proto::internal::v1::SessionPrincipal {
75 user_id: user.id,
76 session_id,
77 security_version: i64::from(security_version),
78 scopes: vec!["event".to_string()],
79 mfa_verified,
80 expires_at_unix_ms: 0,
81 }
82}
Modified lib/auth/src/services/mod.rs lib/auth/src/services/mod.rs +4 −0 View lib/auth/src/services/mod.rs
@@ -2,6 +2,7 @@
22pub mod authority;
33pub mod authority_store;
44pub mod identity;
5pub mod internal_identity;
56pub mod lifecycle;
67pub mod local;
78pub mod login;
@@ -34,6 +35,7 @@
3435 pub(crate) accounts: crate::Accounts,
3536 config: AuthSettings,
3637 mail: Option<MailPublisher>,
38 sessions: crate::session::RedisStore,
3739}
3840
3941impl AuthService {
@@ -41,11 +43,13 @@
4143 accounts: crate::Accounts,
4244 config: AuthSettings,
4345 mail: Option<MailPublisher>,
46 sessions: crate::session::RedisStore,
4447 ) -> Self {
4548 Self {
4649 accounts,
4750 config,
4851 mail,
52 sessions,
4953 }
5054 }
5155
Modified lib/auth/src/lib.rs lib/auth/src/lib.rs +1 −0 View lib/auth/src/lib.rs
@@ -11,6 +11,7 @@
1111pub mod openapi;
1212pub mod pow;
1313pub mod routes;
14pub mod rpc;
1415pub mod services;
1516pub mod share;
1617pub mod templates;
Modified lib/auth/Cargo.toml lib/auth/Cargo.toml +2 −0 View lib/auth/Cargo.toml
@@ -18,7 +18,9 @@
1818events = { workspace = true }
1919cache = { workspace = true }
2020oltp = { workspace = true }
21internal = { workspace = true }
2122proto = { workspace = true }
23tonic = { workspace = true }
2224redis = { workspace = true }
2325queue = { workspace = true }
2426session = { workspace = true }