This commit is contained in:
lix-2026
2026-05-16 12:34:48 +08:00
parent f9336257a5
commit d8bfaea306
17 changed files with 2817 additions and 543 deletions
+69 -9
View File
@@ -111,25 +111,60 @@ fn build_authorization(config: &AppConfig, context: &RequestContext) -> Result<S
.with_header("x-upstream-service", "convex")
})?;
let dev_user_id = config.dev_user_id.trim();
if dev_user_id.is_empty() {
let Some(identity_user) = fallback_acting_identity_user(config, context) else {
return Ok(format!("Convex {admin_key}"));
}
};
// 说明:Rust Web 直接调用 Convex HTTP API 时没有 Next/Convex Auth cookie。
// 自托管开发态用 admin auth 携带 acting identity让 @convex-dev/auth 的
// getAuthUserId(ctx) 得到 DEV_USER_ID,从而和 Next 开发态免登录语义一致
// 自托管开发态和 Hermes 委托 tool 调用都用 admin auth 携带 acting identity
// 让 @convex-dev/auth 的 getAuthUserId(ctx) 得到实际执行用户
let identity = json!({
"subject": format!("{}|mnote-web-dev-session", dev_user_id),
"issuer": "mnote-web-dev",
"name": config.dev_user_name,
"email": config.dev_user_email,
"subject": format!("{}|{}", identity_user.user_id, identity_user.session_suffix),
"issuer": identity_user.issuer,
"name": identity_user.name,
"email": identity_user.email,
});
let identity_encoded =
base64::engine::general_purpose::STANDARD.encode(identity.to_string().as_bytes());
Ok(format!("Convex {admin_key}:{identity_encoded}"))
}
struct ActingIdentityUser {
user_id: String,
session_suffix: &'static str,
issuer: &'static str,
name: String,
email: String,
}
fn fallback_acting_identity_user(
config: &AppConfig,
context: &RequestContext,
) -> Option<ActingIdentityUser> {
let actor_id = context.auth.actor_id.trim();
if !actor_id.is_empty() && actor_id != "anonymous" {
return Some(ActingIdentityUser {
user_id: actor_id.to_string(),
session_suffix: "mnote-web-delegated-session",
issuer: "mnote-web-delegated",
name: actor_id.to_string(),
email: String::new(),
});
}
let dev_user_id = config.dev_user_id.trim();
if dev_user_id.is_empty() {
return None;
}
Some(ActingIdentityUser {
user_id: dev_user_id.to_string(),
session_suffix: "mnote-web-dev-session",
issuer: "mnote-web-dev",
name: config.dev_user_name.clone(),
email: config.dev_user_email.clone(),
})
}
fn extract_cookie_value(context: &RequestContext, name: &str) -> Option<String> {
context
.auth
@@ -1175,6 +1210,31 @@ mod tests {
assert_eq!(identity["email"], "dev@mnote.local");
}
#[test]
fn build_authorization_uses_delegated_actor_for_admin_identity() {
let mut headers = HeaderMap::new();
headers.insert("x-mnote-actor-id", HeaderValue::from_static("user_real_1"));
headers.insert("x-mnote-actor-type", HeaderValue::from_static("user"));
let authorization =
build_authorization(&config(), &request_context(headers)).expect("authorization");
assert!(authorization.starts_with("Convex admin-demo:"));
let encoded = authorization
.trim_start_matches("Convex admin-demo:")
.trim();
let decoded = base64::engine::general_purpose::STANDARD
.decode(encoded)
.expect("identity base64");
let identity: serde_json::Value = serde_json::from_slice(&decoded).expect("identity json");
assert_eq!(
identity["subject"],
"user_real_1|mnote-web-delegated-session"
);
assert_eq!(identity["issuer"], "mnote-web-delegated");
assert_eq!(identity["name"], "user_real_1");
}
#[test]
fn build_authorization_reads_convex_token_from_cookie() {
let mut headers = HeaderMap::new();