feat(enterprise): route four domains through lighthouse
This commit is contained in:
parent
1d2a8a2f63
commit
c058974bba
12 changed files with 707 additions and 20 deletions
|
|
@ -15,7 +15,7 @@ use uuid::Uuid;
|
|||
|
||||
const SNAPSHOT_SCHEMA: &str = "hololake.code-channel/v1";
|
||||
const REGISTRY_SCHEMA: &str = "hololake.code-channel-registry/v1";
|
||||
const ALLOWED_HOSTS: &[&str] = &["guanghulab.com", "guanghubingshuo.com"];
|
||||
const ALLOWED_HOSTS: &[&str] = &["guanghulab.com", "guanghubingshuo.com", "guanghu.chat"];
|
||||
const MAX_TREE_ENTRIES: usize = 1_000;
|
||||
const MAX_CODE_FILE_BYTES: u64 = 2 * 1024 * 1024;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
//! 登录模块 · code_repo_login
|
||||
//!
|
||||
//! 规划卷依据(HoloLake第二阶段总体规划-20260815 · 阶段B1):
|
||||
//! - 人类端输入代码仓库账号密码 → 对 guanghulab(Forgejo)验证。
|
||||
//! - 人类端输入代码仓库账号密码 → 对编号绑定域的 Forgejo 验证。
|
||||
//! - 登录仓库 = 验证了背后绑定的服务器(冰朔教义)。
|
||||
//! - 凭证只存本机钥匙串 · 不落明文。
|
||||
//!
|
||||
//! 事实底账(2026-08-15 三角测量):
|
||||
//! - Forgejo 挂载在 /code 路径下:GET https://{host}/code/api/v1/user 走基本认证,
|
||||
//! 假凭证=401 · 真凭证=200 并回显 JSON(login/email)。
|
||||
//! - guanghulab.com 与 guanghubingshuo.com 双域同路可用(均在 ALLOWED_HOSTS 血统内)。
|
||||
//! - 第五域走 guanghulab.com;企业四域走 guanghu.chat,二者数据与登录入口隔离。
|
||||
//! - 钥匙存取走系统钥匙串(macOS `security`);其余平台暂不落盘密码,
|
||||
//! 会话仅内存保持(诚实边界,Windows 钥匙串接入排在分发阶段)。
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ use tauri::{AppHandle, Manager, State};
|
|||
use crate::zero_point::{self, ZeroPointState};
|
||||
|
||||
const LOGIN_HOST: &str = "guanghulab.com";
|
||||
const ENTERPRISE_LOGIN_HOST: &str = "guanghu.chat";
|
||||
const SESSION_FILE_NAME: &str = "login-session.json";
|
||||
|
||||
/// 落盘的登录会话——只有用户名与主机,密码永不落盘。
|
||||
|
|
@ -168,7 +169,7 @@ fn login_host_for_domain(domain: &str) -> Result<&'static str, String> {
|
|||
match domain {
|
||||
"FIFTH_DOMAIN" => Ok(LOGIN_HOST),
|
||||
"MAIN_DOMAIN" | "BRANCH_DOMAIN" | "ZERO_DOMAIN" | "ZERO_SENSE_DOMAIN" => {
|
||||
Err("HOLOLAKE_DOMAIN_LOGIN_NOT_PROVISIONED".into())
|
||||
Ok(ENTERPRISE_LOGIN_HOST)
|
||||
}
|
||||
_ => Err("HOLOLAKE_DOMAIN_ROUTE_INVALID".into()),
|
||||
}
|
||||
|
|
@ -204,7 +205,7 @@ pub async fn perform_code_repo_login(
|
|||
.await
|
||||
.map_err(|error| format!("HOLOLAKE_LOGIN_NETWORK_FAILED: {error}"))?;
|
||||
let status = response.status();
|
||||
if status == reqwest::StatusCode::UNAUTHORIZED {
|
||||
if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN {
|
||||
return Err("HOLOLAKE_LOGIN_CREDENTIALS_INVALID".into());
|
||||
}
|
||||
if !status.is_success() {
|
||||
|
|
@ -279,8 +280,8 @@ mod tests {
|
|||
"ZERO_SENSE_DOMAIN",
|
||||
] {
|
||||
assert_eq!(
|
||||
login_host_for_domain(domain).unwrap_err(),
|
||||
"HOLOLAKE_DOMAIN_LOGIN_NOT_PROVISIONED"
|
||||
login_host_for_domain(domain).unwrap(),
|
||||
ENTERPRISE_LOGIN_HOST
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ pub struct ZeroPointProtocol {
|
|||
pub lighthouse_anchor_url: String,
|
||||
#[serde(default = "default_resolve_url")]
|
||||
pub lighthouse_resolve_url: String,
|
||||
#[serde(default = "default_enterprise_resolve_url")]
|
||||
pub enterprise_resolve_url: String,
|
||||
#[serde(default = "default_core_source")]
|
||||
pub core_channel_source: String,
|
||||
#[serde(default = "default_protocol_origin")]
|
||||
|
|
@ -37,6 +39,9 @@ fn default_anchor_url() -> String {
|
|||
fn default_resolve_url() -> String {
|
||||
"https://guanghulab.com/api/ai/v1/resolve?id=".into()
|
||||
}
|
||||
fn default_enterprise_resolve_url() -> String {
|
||||
"https://guanghu.chat/api/hololake/enterprise/resolve".into()
|
||||
}
|
||||
fn default_core_source() -> String {
|
||||
"https://guanghulab.com/code/bingshuo/guanghu-ice-heart".into()
|
||||
}
|
||||
|
|
@ -50,6 +55,7 @@ impl Default for ZeroPointProtocol {
|
|||
grace_period_days: default_grace_days(),
|
||||
lighthouse_anchor_url: default_anchor_url(),
|
||||
lighthouse_resolve_url: default_resolve_url(),
|
||||
enterprise_resolve_url: default_enterprise_resolve_url(),
|
||||
core_channel_source: default_core_source(),
|
||||
origin: default_protocol_origin(),
|
||||
}
|
||||
|
|
@ -319,12 +325,9 @@ pub async fn zero_point_verify(
|
|||
state: State<'_, ZeroPointState>,
|
||||
) -> Result<ZeroPointSnapshot, String> {
|
||||
let home = home_of(&state)?;
|
||||
let (number, resolve_url) = {
|
||||
let (number, protocol) = {
|
||||
let inner = lock(&state)?;
|
||||
(
|
||||
inner.user_number.clone(),
|
||||
inner.protocol.lighthouse_resolve_url.clone(),
|
||||
)
|
||||
(inner.user_number.clone(), inner.protocol.clone())
|
||||
};
|
||||
if number.is_empty() {
|
||||
append_heartbeat(&home, "verify verdict=REJECT reason=waiting_binding");
|
||||
|
|
@ -335,7 +338,8 @@ pub async fn zero_point_verify(
|
|||
.timeout(Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
|
||||
let (verdict, resolution) = match client.get(format!("{resolve_url}{number}")).send().await {
|
||||
let resolve_url = resolver_url_for_number(&protocol, &number)?;
|
||||
let (verdict, resolution) = match client.get(resolve_url).send().await {
|
||||
Ok(resp) => {
|
||||
let ok = resp.status().is_success();
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
|
|
@ -386,6 +390,33 @@ pub async fn zero_point_verify(
|
|||
zero_point_status(state).await
|
||||
}
|
||||
|
||||
fn resolver_url_for_number(
|
||||
protocol: &ZeroPointProtocol,
|
||||
number: &str,
|
||||
) -> Result<reqwest::Url, String> {
|
||||
let mut url = if number.starts_with("TCS-GL-") {
|
||||
reqwest::Url::parse(&protocol.enterprise_resolve_url)
|
||||
} else {
|
||||
reqwest::Url::parse(&protocol.lighthouse_resolve_url)
|
||||
}
|
||||
.map_err(|_| "HOLOLAKE_ZP_RESOLVER_URL_INVALID".to_string())?;
|
||||
if number.starts_with("TCS-GL-") {
|
||||
url.query_pairs_mut().append_pair("id", number);
|
||||
} else {
|
||||
// 第五域旧协议以 `?id=` 结尾;使用 URL 查询构造器避免把编号中的字符裸拼入地址。
|
||||
let clean_path = url.path().to_string();
|
||||
let existing = url.query().unwrap_or("");
|
||||
if existing == "id=" || existing.is_empty() {
|
||||
url.set_query(None);
|
||||
url.set_path(&clean_path);
|
||||
url.query_pairs_mut().append_pair("id", number);
|
||||
} else {
|
||||
return Err("HOLOLAKE_ZP_RESOLVER_URL_INVALID".into());
|
||||
}
|
||||
}
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct LighthouseResolution {
|
||||
name: String,
|
||||
|
|
@ -539,6 +570,30 @@ mod tests {
|
|||
assert!(protocol
|
||||
.lighthouse_resolve_url
|
||||
.starts_with("https://guanghulab.com"));
|
||||
assert!(protocol
|
||||
.enterprise_resolve_url
|
||||
.starts_with("https://guanghu.chat"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn human_number_selects_its_authoritative_registry() {
|
||||
let protocol = ZeroPointProtocol::default();
|
||||
let fifth = resolver_url_for_number(&protocol, "ICE-GL∞").unwrap();
|
||||
assert_eq!(fifth.host_str(), Some("guanghulab.com"));
|
||||
assert_eq!(
|
||||
fifth.query_pairs().find(|(key, _)| key == "id").unwrap().1,
|
||||
"ICE-GL∞"
|
||||
);
|
||||
let enterprise = resolver_url_for_number(&protocol, "TCS-GL-0007∞").unwrap();
|
||||
assert_eq!(enterprise.host_str(), Some("guanghu.chat"));
|
||||
assert_eq!(
|
||||
enterprise
|
||||
.query_pairs()
|
||||
.find(|(key, _)| key == "id")
|
||||
.unwrap()
|
||||
.1,
|
||||
"TCS-GL-0007∞"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue