feat(backend): orchestrate federation runtime for shared middle and exit nodes

This commit is contained in:
sagit
2026-02-10 02:34:20 +00:00
parent 5f42c50689
commit 79f8aab600
8 changed files with 1406 additions and 30 deletions
+125 -12
View File
@@ -27,6 +27,9 @@ type Handler struct {
jwtSecret string
wsServer *ws.Server
captchaMu sync.Mutex
captchaTokens map[string]int64
jobsMu sync.Mutex
jobsCancel context.CancelFunc
jobsStarted bool
@@ -39,6 +42,11 @@ type loginRequest struct {
CaptchaID string `json:"captchaId"`
}
type captchaVerifyRequest struct {
ID string `json:"id"`
Data string `json:"data"`
}
type nameRequest struct {
Name string `json:"name"`
}
@@ -63,9 +71,10 @@ type flowItem struct {
func New(repo *sqlite.Repository, jwtSecret string) *Handler {
return &Handler{
repo: repo,
jwtSecret: jwtSecret,
wsServer: ws.NewServer(repo, jwtSecret),
repo: repo,
jwtSecret: jwtSecret,
wsServer: ws.NewServer(repo, jwtSecret),
captchaTokens: make(map[string]int64),
}
}
@@ -85,6 +94,7 @@ func (h *Handler) Register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/config/update", h.updateConfigs)
mux.HandleFunc("/api/v1/config/update-single", h.updateSingleConfig)
mux.HandleFunc("/api/v1/captcha/check", h.checkCaptcha)
mux.HandleFunc("/api/v1/captcha/verify", h.captchaVerify)
mux.HandleFunc("/api/v1/user/package", h.userPackage)
mux.HandleFunc("/api/v1/user/updatePassword", h.updatePassword)
mux.HandleFunc("/api/v1/node/list", h.nodeList)
@@ -148,6 +158,9 @@ func (h *Handler) Register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/federation/share/delete", h.federationShareDelete)
mux.HandleFunc("/api/v1/federation/connect", h.authPeer(h.federationConnect))
mux.HandleFunc("/api/v1/federation/tunnel/create", h.authPeer(h.federationTunnelCreate))
mux.HandleFunc("/api/v1/federation/runtime/reserve-port", h.authPeer(h.federationRuntimeReservePort))
mux.HandleFunc("/api/v1/federation/runtime/apply-role", h.authPeer(h.federationRuntimeApplyRole))
mux.HandleFunc("/api/v1/federation/runtime/release-role", h.authPeer(h.federationRuntimeReleaseRole))
mux.HandleFunc("/api/v1/federation/node/import", h.nodeImport)
mux.HandleFunc("/flow/test", h.flowTest)
@@ -183,20 +196,23 @@ func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
return
}
if captchaEnabled {
if strings.TrimSpace(req.CaptchaID) == "" {
captchaID := strings.TrimSpace(req.CaptchaID)
if captchaID == "" {
response.WriteJSON(w, response.ErrDefault("验证码校验失败"))
return
}
secretCfg, err := h.repo.GetConfigByName("cloudflare_secret_key")
if err != nil || secretCfg == nil || secretCfg.Value == "" {
response.WriteJSON(w, response.ErrDefault("验证码配置错误:未配置Secret Key"))
return
}
if !h.consumeCaptchaToken(captchaID) {
secretCfg, err := h.repo.GetConfigByName("cloudflare_secret_key")
if err != nil || secretCfg == nil || strings.TrimSpace(secretCfg.Value) == "" {
response.WriteJSON(w, response.ErrDefault("验证码校验失败"))
return
}
if !h.verifyCloudflareTurnstile(req.CaptchaID, secretCfg.Value) {
response.WriteJSON(w, response.ErrDefault("验证码校验失败"))
return
if !h.verifyCloudflareTurnstile(captchaID, strings.TrimSpace(secretCfg.Value)) {
response.WriteJSON(w, response.ErrDefault("验证码校验失败"))
return
}
}
}
@@ -585,6 +601,40 @@ func (h *Handler) checkCaptcha(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, response.OK(0))
}
func (h *Handler) captchaVerify(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
response.WriteJSON(w, response.ErrDefault("请求失败"))
return
}
var req captchaVerifyRequest
if err := decodeJSON(r.Body, &req); err != nil {
h.writeCaptchaVerifyResult(w, false, "")
return
}
id := strings.TrimSpace(req.ID)
data := strings.TrimSpace(req.Data)
if id == "" || data == "" {
h.writeCaptchaVerifyResult(w, false, "")
return
}
verified := false
secretCfg, err := h.repo.GetConfigByName("cloudflare_secret_key")
if err == nil && secretCfg != nil && strings.TrimSpace(secretCfg.Value) != "" {
verified = h.verifyCloudflareTurnstile(data, strings.TrimSpace(secretCfg.Value))
} else {
verified = data == "ok"
}
if !verified {
h.writeCaptchaVerifyResult(w, false, "")
return
}
h.markCaptchaToken(id)
h.writeCaptchaVerifyResult(w, true, id)
}
func (h *Handler) flowTest(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("test"))
@@ -899,6 +949,69 @@ func (h *Handler) captchaEnabled() (bool, error) {
return strings.EqualFold(cfg.Value, "true"), nil
}
func (h *Handler) markCaptchaToken(token string) {
if h == nil {
return
}
token = strings.TrimSpace(token)
if token == "" {
return
}
now := time.Now().UnixMilli()
exp := now + int64(5*time.Minute/time.Millisecond)
h.captchaMu.Lock()
defer h.captchaMu.Unlock()
if h.captchaTokens == nil {
h.captchaTokens = make(map[string]int64)
}
for k, v := range h.captchaTokens {
if v <= now {
delete(h.captchaTokens, k)
}
}
h.captchaTokens[token] = exp
}
func (h *Handler) consumeCaptchaToken(token string) bool {
if h == nil {
return false
}
token = strings.TrimSpace(token)
if token == "" {
return false
}
now := time.Now().UnixMilli()
h.captchaMu.Lock()
defer h.captchaMu.Unlock()
if h.captchaTokens == nil {
return false
}
for k, v := range h.captchaTokens {
if v <= now {
delete(h.captchaTokens, k)
}
}
exp, ok := h.captchaTokens[token]
if !ok {
return false
}
delete(h.captchaTokens, token)
return exp > now
}
func (h *Handler) writeCaptchaVerifyResult(w http.ResponseWriter, success bool, token string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
payload := map[string]interface{}{
"success": success,
"data": map[string]interface{}{
"validToken": token,
},
}
_ = json.NewEncoder(w).Encode(payload)
}
func decodeJSON(body io.ReadCloser, out interface{}) error {
defer body.Close()
decoder := json.NewDecoder(body)