收口 Rust Web 入口与 AI 写入链
- 将 3000 主入口继续收口到 mnote-web,补齐 /favicon.ico、/api/auth、session alias、AI run 等 Rust Web 路由边界。 - 更新登录页与 Convex Auth 代理,支持测试账号快速登录写入真实 Convex Auth cookie。 - 推进页面设置、Wolai 对齐、Phase 7 AI kernel/CLI-first 设计文档与相关 smoke 脚本。 - 更新 leptos-tiptap 生成资产、mnote-cli/bridge-runtime、前端依赖和 dev/prod 启动脚本。
This commit is contained in:
@@ -28,6 +28,7 @@ pub struct SessionResponse {
|
||||
pub email: String,
|
||||
pub name: String,
|
||||
pub actor_type: String,
|
||||
pub auth_mode: &'static str,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
}
|
||||
@@ -47,36 +48,57 @@ pub async fn session(
|
||||
State(state): State<AppState>,
|
||||
Extension(context): Extension<RequestContext>,
|
||||
) -> Response {
|
||||
owner_json(Json(SessionResponse {
|
||||
ok: true,
|
||||
owner: "mnote-web",
|
||||
user_id: state.config().dev_user_id.clone(),
|
||||
email: state.config().dev_user_email.clone(),
|
||||
name: state.config().dev_user_name.clone(),
|
||||
actor_type: context.auth.actor_type,
|
||||
request_id: context.trace.request_id,
|
||||
trace_id: context.trace.trace_id,
|
||||
}))
|
||||
owner_json(Json(build_session_response(&state, context)))
|
||||
}
|
||||
|
||||
pub async fn refresh_session(
|
||||
State(state): State<AppState>,
|
||||
Extension(context): Extension<RequestContext>,
|
||||
) -> Response {
|
||||
let mut response = owner_json(Json(SessionResponse {
|
||||
ok: true,
|
||||
owner: "mnote-web",
|
||||
user_id: state.config().dev_user_id.clone(),
|
||||
email: state.config().dev_user_email.clone(),
|
||||
name: state.config().dev_user_name.clone(),
|
||||
actor_type: context.auth.actor_type,
|
||||
request_id: context.trace.request_id,
|
||||
trace_id: context.trace.trace_id,
|
||||
}));
|
||||
let mut response = owner_json(Json(build_session_response(&state, context)));
|
||||
*response.status_mut() = StatusCode::OK;
|
||||
response
|
||||
}
|
||||
|
||||
fn build_session_response(state: &AppState, context: RequestContext) -> SessionResponse {
|
||||
let actor_id = context.auth.actor_id.trim();
|
||||
let has_forwarded_actor = !actor_id.is_empty() && actor_id != "anonymous";
|
||||
let user_id = if has_forwarded_actor {
|
||||
actor_id.to_string()
|
||||
} else {
|
||||
state.config().dev_user_id.clone()
|
||||
};
|
||||
let actor_type = if has_forwarded_actor {
|
||||
context.auth.actor_type
|
||||
} else {
|
||||
"devFallback".to_string()
|
||||
};
|
||||
|
||||
SessionResponse {
|
||||
ok: true,
|
||||
owner: "mnote-web",
|
||||
user_id,
|
||||
email: if has_forwarded_actor {
|
||||
String::new()
|
||||
} else {
|
||||
state.config().dev_user_email.clone()
|
||||
},
|
||||
name: if has_forwarded_actor {
|
||||
actor_id.to_string()
|
||||
} else {
|
||||
state.config().dev_user_name.clone()
|
||||
},
|
||||
actor_type,
|
||||
auth_mode: if has_forwarded_actor {
|
||||
"forwardedActor"
|
||||
} else {
|
||||
"devFallback"
|
||||
},
|
||||
request_id: context.trace.request_id,
|
||||
trace_id: context.trace.trace_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn owner_json<T>(payload: Json<T>) -> Response
|
||||
where
|
||||
T: Serialize,
|
||||
@@ -172,4 +194,100 @@ mod tests {
|
||||
assert_eq!(payload["userId"], "dev-user");
|
||||
assert!(payload.get("convexAdminKey").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_prefers_forwarded_actor_identity_over_dev_fallback() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/session")
|
||||
.header("x-mnote-actor-id", "user_real")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: serde_json::Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["owner"], "mnote-web");
|
||||
assert_eq!(payload["userId"], "user_real");
|
||||
assert_eq!(payload["actorType"], "user");
|
||||
assert_eq!(payload["authMode"], "forwardedActor");
|
||||
assert!(payload.get("convexAdminKey").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_whoami_alias_prefers_forwarded_actor_identity() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/whoami")
|
||||
.header("x-mnote-actor-id", "user_real")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: serde_json::Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["owner"], "mnote-web");
|
||||
assert_eq!(payload["userId"], "user_real");
|
||||
assert_eq!(payload["actorType"], "user");
|
||||
assert_eq!(payload["authMode"], "forwardedActor");
|
||||
assert_ne!(payload["code"], "legacy_next_compat_disabled");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_whoami_alias_returns_dev_identity() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/whoami")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: serde_json::Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["owner"], "mnote-web");
|
||||
assert_eq!(payload["userId"], "dev-user");
|
||||
assert_ne!(payload["code"], "legacy_next_compat_disabled");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_mnote_web_token_alias_returns_dev_identity() {
|
||||
let response = app()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/mnote-web-token")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let payload: serde_json::Value = serde_json::from_slice(&body).expect("json");
|
||||
assert_eq!(payload["owner"], "mnote-web");
|
||||
assert_eq!(payload["userId"], "dev-user");
|
||||
assert_ne!(payload["code"], "legacy_next_compat_disabled");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user