fix(render,store): frpc user 支持 + 修正 useEncryption/useCompression 渲染位置

- store.Remote 新增 user 字段(DB 列名 usr,避开保留字;migrate() 自动加列),
  渲染为 frpc 全局 user。面板型 frps(如 ChhChongGou 实例)强制要求 user 登录,
  缺失时服务端返回 panel authentication unavailable: panel HTTP status 403
- ★ 修复渲染 bug:useEncryption/useCompression/bandwidthLimit 必须内嵌在 proxy 的
  transport 对象(frpc ProxyTransport)。此前渲染在 proxy 顶层,frpc 默认
  --strict-config 直接拒载:unknown field "useEncryption"。任何启用加密/压缩
  的转发都无法启动。poolCount 无 proxy 级表达(官方 ProxyTransport 无此字段),
  保留在 store 但不再渲染
- 前端:RemoteNode 画布节点与 StatusView 表单加 user 输入(画布此前会静默
  清掉未声明的字段),带『面板型 frps 必填』提示
- 测试:user 往返(含迁移路径)、渲染含/不含 user、transport 内嵌与顶层
  非法键回归守卫
This commit is contained in:
JianFeeeee
2026-10-12 19:16:28 +08:00
parent b29cd0d186
commit d83e5d8e70
7 changed files with 214 additions and 36 deletions

View File

@ -58,3 +58,63 @@ func TestReplaceLinksClearsOld(t *testing.T) {
t.Fatalf("links = %+v", links)
}
}
// `usr` is a reserved SQL keyword in several dialects, so the remotes column is
// named `usr`. This guards the mapping in both directions, including the
// migrate() path that adds the column to pre-existing databases.
func TestRemoteUserRoundTrip(t *testing.T) {
st, err := New(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
// A remote written without user (existing installs) must read back empty.
if err := st.UpsertRemote(Remote{Name: "plain", IP: "1.2.3.4", Port: 7000, Enabled: true}); err != nil {
t.Fatal(err)
}
if got, ok := st.GetRemote("plain"); !ok || got.User != "" {
t.Fatalf("plain remote user = %q ok=%v, want empty", got.User, ok)
}
want := Remote{Name: "panel", IP: "103.40.13.126", Port: 59999, Token: "tk", User: "e8f52adfaf0e5c95", Enabled: true}
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
got, ok := st.GetRemote("panel")
if !ok || got.User != want.User {
t.Fatalf("user not persisted: %+v", got)
}
list, err := st.ListRemotes()
if err != nil {
t.Fatal(err)
}
var found bool
for _, r := range list {
if r.Name == "panel" {
found = true
if r.User != want.User {
t.Fatalf("ListRemotes user = %q, want %q", r.User, want.User)
}
}
}
if !found {
t.Fatal("panel remote missing from ListRemotes")
}
// Updating in place must not drop the column, and clearing it must work.
want.Token = "tk2"
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
if got, _ := st.GetRemote("panel"); got.User != want.User || got.Token != "tk2" {
t.Fatalf("upsert lost fields: %+v", got)
}
want.User = ""
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
if got, _ := st.GetRemote("panel"); got.User != "" {
t.Fatalf("user not cleared: %q", got.User)
}
}