advance 1-8 post-mvp execution batches
This commit is contained in:
@@ -41,6 +41,8 @@ const COOKIE_MNOTE_WEB_CONVEX_TOKEN: &str = "mnote_web_convex_token";
|
||||
const COOKIE_MNOTE_WEB_DEV_SESSION: &str = "mnote_web_dev_session";
|
||||
const COOKIE_MNOTE_ACTOR_ID: &str = "mnote_actor_id";
|
||||
const COOKIE_MNOTE_ACTOR_TYPE: &str = "mnote_actor_type";
|
||||
const COOKIE_MNOTE_ACTOR_EMAIL: &str = "mnote_actor_email";
|
||||
const COOKIE_MNOTE_ACTOR_NAME: &str = "mnote_actor_name";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -121,9 +123,12 @@ pub async fn auth_api(
|
||||
.with_context(&context)
|
||||
.with_header(HEADER_MNOTE_WEB_OWNER, "mnote-web"));
|
||||
}
|
||||
if action == "auth:signOut" {
|
||||
return Ok(build_sign_out_response(&context));
|
||||
}
|
||||
|
||||
let convex_response = run_convex_auth_action(&state, &context, &payload).await?;
|
||||
Ok(build_auth_proxy_response(&convex_response, &context))
|
||||
Ok(build_auth_proxy_response(&convex_response, &context, &payload))
|
||||
}
|
||||
|
||||
pub async fn auth_entry(
|
||||
@@ -1625,6 +1630,7 @@ async fn run_convex_auth_action(
|
||||
fn build_auth_proxy_response(
|
||||
convex_response: &serde_json::Value,
|
||||
context: &RequestContext,
|
||||
request_payload: &serde_json::Value,
|
||||
) -> Response {
|
||||
if convex_response
|
||||
.get("status")
|
||||
@@ -1639,7 +1645,7 @@ fn build_auth_proxy_response(
|
||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||
clear_auth_cookies(response.headers_mut());
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
context.apply_response_headers(response.headers_mut());
|
||||
apply_trace_response_headers(context, response.headers_mut());
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1679,14 +1685,46 @@ fn build_auth_proxy_response(
|
||||
set_literal_cookie(response.headers_mut(), COOKIE_MNOTE_ACTOR_ID, &actor_id);
|
||||
set_literal_cookie(response.headers_mut(), COOKIE_MNOTE_ACTOR_TYPE, "user");
|
||||
}
|
||||
if let Some(email) = resolve_mnote_actor_email(&value, tokens, request_payload) {
|
||||
set_encoded_cookie(response.headers_mut(), COOKIE_MNOTE_ACTOR_EMAIL, &email);
|
||||
}
|
||||
if let Some(name) = resolve_mnote_actor_name(&value, tokens, request_payload) {
|
||||
set_encoded_cookie(response.headers_mut(), COOKIE_MNOTE_ACTOR_NAME, &name);
|
||||
}
|
||||
expire_cookie(response.headers_mut(), COOKIE_MNOTE_WEB_DEV_SESSION);
|
||||
}
|
||||
}
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
context.apply_response_headers(response.headers_mut());
|
||||
apply_trace_response_headers(context, response.headers_mut());
|
||||
response
|
||||
}
|
||||
|
||||
fn build_sign_out_response(context: &RequestContext) -> Response {
|
||||
let mut response = axum::Json(json!({ "ok": true, "signedOut": true })).into_response();
|
||||
clear_auth_cookies(response.headers_mut());
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
apply_trace_response_headers(context, response.headers_mut());
|
||||
response
|
||||
}
|
||||
|
||||
fn apply_trace_response_headers(context: &RequestContext, headers: &mut axum::http::HeaderMap) {
|
||||
insert_response_header(headers, "x-request-id", &context.trace.request_id);
|
||||
insert_response_header(headers, "x-trace-id", &context.trace.trace_id);
|
||||
if let Some(workspace_id) = &context.workspace.workspace_id {
|
||||
insert_response_header(headers, "x-mnote-workspace-id", workspace_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_response_header(headers: &mut axum::http::HeaderMap, name: &str, value: &str) {
|
||||
let Ok(name) = HeaderName::from_lowercase(name.as_bytes()) else {
|
||||
return;
|
||||
};
|
||||
let Ok(value) = HeaderValue::from_str(value) else {
|
||||
return;
|
||||
};
|
||||
headers.insert(name, value);
|
||||
}
|
||||
|
||||
fn resolve_mnote_actor_id(
|
||||
value: &serde_json::Value,
|
||||
tokens: &serde_json::Value,
|
||||
@@ -1735,6 +1773,69 @@ fn extract_actor_id_from_jwt(token: &str) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_mnote_actor_email(
|
||||
value: &serde_json::Value,
|
||||
tokens: &serde_json::Value,
|
||||
request_payload: &serde_json::Value,
|
||||
) -> Option<String> {
|
||||
[
|
||||
"/email",
|
||||
"/user/email",
|
||||
"/profile/email",
|
||||
"/args/params/email",
|
||||
]
|
||||
.iter()
|
||||
.find_map(|pointer| non_empty_json_string(value.pointer(pointer)))
|
||||
.or_else(|| {
|
||||
tokens
|
||||
.get("token")
|
||||
.and_then(Value::as_str)
|
||||
.and_then(extract_email_from_jwt)
|
||||
})
|
||||
.or_else(|| non_empty_json_string(request_payload.pointer("/args/params/email")))
|
||||
}
|
||||
|
||||
fn resolve_mnote_actor_name(
|
||||
value: &serde_json::Value,
|
||||
tokens: &serde_json::Value,
|
||||
request_payload: &serde_json::Value,
|
||||
) -> Option<String> {
|
||||
["/name", "/user/name", "/profile/name", "/args/params/name"]
|
||||
.iter()
|
||||
.find_map(|pointer| non_empty_json_string(value.pointer(pointer)))
|
||||
.or_else(|| {
|
||||
tokens
|
||||
.get("token")
|
||||
.and_then(Value::as_str)
|
||||
.and_then(extract_name_from_jwt)
|
||||
})
|
||||
.or_else(|| non_empty_json_string(request_payload.pointer("/args/params/name")))
|
||||
}
|
||||
|
||||
fn extract_email_from_jwt(token: &str) -> Option<String> {
|
||||
jwt_string_claim(token, &["email", "preferred_username"])
|
||||
}
|
||||
|
||||
fn extract_name_from_jwt(token: &str) -> Option<String> {
|
||||
jwt_string_claim(token, &["name", "username"])
|
||||
}
|
||||
|
||||
fn jwt_string_claim(token: &str, keys: &[&str]) -> Option<String> {
|
||||
let payload_segment = token.split('.').nth(1)?;
|
||||
let decoded = URL_SAFE_NO_PAD.decode(payload_segment.as_bytes()).ok()?;
|
||||
let payload: Value = serde_json::from_slice(&decoded).ok()?;
|
||||
keys.iter()
|
||||
.find_map(|key| non_empty_json_string(payload.get(key)))
|
||||
}
|
||||
|
||||
fn non_empty_json_string(value: Option<&serde_json::Value>) -> Option<String> {
|
||||
value
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn set_literal_cookie(headers: &mut axum::http::HeaderMap, name: &'static str, value: &str) {
|
||||
let cookie = format!("{name}={value}; Path=/; HttpOnly; SameSite=Lax");
|
||||
if let Ok(value) = HeaderValue::from_str(&cookie) {
|
||||
@@ -1742,6 +1843,11 @@ fn set_literal_cookie(headers: &mut axum::http::HeaderMap, name: &'static str, v
|
||||
}
|
||||
}
|
||||
|
||||
fn set_encoded_cookie(headers: &mut axum::http::HeaderMap, name: &'static str, value: &str) {
|
||||
let encoded = URL_SAFE_NO_PAD.encode(value.as_bytes());
|
||||
set_literal_cookie(headers, name, &encoded);
|
||||
}
|
||||
|
||||
fn set_auth_cookie_from_value(
|
||||
headers: &mut axum::http::HeaderMap,
|
||||
name: &'static str,
|
||||
@@ -1762,6 +1868,8 @@ fn clear_auth_cookies(headers: &mut axum::http::HeaderMap) {
|
||||
expire_cookie(headers, COOKIE_MNOTE_WEB_DEV_SESSION);
|
||||
expire_cookie(headers, COOKIE_MNOTE_ACTOR_ID);
|
||||
expire_cookie(headers, COOKIE_MNOTE_ACTOR_TYPE);
|
||||
expire_cookie(headers, COOKIE_MNOTE_ACTOR_EMAIL);
|
||||
expire_cookie(headers, COOKIE_MNOTE_ACTOR_NAME);
|
||||
}
|
||||
|
||||
fn expire_cookie(headers: &mut axum::http::HeaderMap, name: &'static str) {
|
||||
@@ -2623,9 +2731,9 @@ mod tests {
|
||||
assert!(html.contains("local_folder"));
|
||||
assert!(html.contains(r#"data-mnote-source-kind="local_folder""#));
|
||||
assert!(html.contains(r#"data-mnote-root-uri="file://"#));
|
||||
assert!(html.contains(r#"data-testid="mnote-workspace-empty-state""#));
|
||||
assert!(html.contains("当前还没有可显示的本地工作区"));
|
||||
assert!(html.contains(r#"data-testid="mnote-empty-create-page""#));
|
||||
assert!(!html.contains(r#"data-testid="mnote-workspace-empty-state""#));
|
||||
assert!(!html.contains("当前还没有可显示的本地工作区"));
|
||||
assert!(!html.contains(r#"data-testid="mnote-empty-create-page""#));
|
||||
assert!(html.contains(r#""transport":"disabled""#));
|
||||
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
@@ -2797,7 +2905,9 @@ mod tests {
|
||||
.expect("body");
|
||||
let html = String::from_utf8(body.to_vec()).expect("utf8");
|
||||
assert!(html.contains(r#"data-mnote-shell="auth""#));
|
||||
assert!(html.contains("邮箱登录"));
|
||||
assert!(html.contains("账号登录"));
|
||||
assert!(html.contains("邮箱或用户名"));
|
||||
assert!(!html.contains(r#"<span>"用户名"</span>"#));
|
||||
assert!(html.contains("测试账号快速登录"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user