feat: Z34b restore-by-host + harden web nginx DNS race

Add HMAC restore-by-host for phone-less rebind; resolve gateway at request time and recreate web after stack up.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-07 00:16:08 +08:00
parent f0fcc1fbda
commit e571e98387
12 changed files with 492 additions and 30 deletions

View File

@@ -46,6 +46,7 @@ func RegisterHandlers(server *rest.Server, svcCtx *svc.ServiceContext) {
{Method: http.MethodPost, Path: "/api/v1/auth/bind/phone-confirm", Handler: rl(bindPhoneConfirmHandler(svcCtx))},
{Method: http.MethodGet, Path: "/api/v1/auth/bind/policy", Handler: rl(bindPolicyHandler(svcCtx))},
{Method: http.MethodPost, Path: "/api/v1/auth/yuheng/ticket-exchange", Handler: rl(yuhengTicketExchangeHandler(svcCtx))},
{Method: http.MethodPost, Path: "/api/v1/auth/yuheng/restore-by-host", Handler: rl(yuhengRestoreByHostHandler(svcCtx))},
{Method: http.MethodPost, Path: "/api/v1/auth/register", Handler: rl(registerHandler(svcCtx))},
{Method: http.MethodPost, Path: "/api/v1/auth/login", Handler: rl(loginHandler(svcCtx))},
{Method: http.MethodPost, Path: "/api/v1/auth/sms/send", Handler: rl(sendLoginSMSHandler(svcCtx))},
@@ -146,8 +147,6 @@ func RegisterHandlers(server *rest.Server, svcCtx *svc.ServiceContext) {
{Method: http.MethodGet, Path: "/api/v1/admin/sync/conflicts", Handler: chain(syncConflictsHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodPost, Path: "/api/v1/admin/sync/conflicts/:id/resolve", Handler: chain(syncResolveConflictHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/reconcile", Handler: chain(syncReconcileHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodGet, Path: "/api/v1/admin/sync/channels/:id/checkpoint", Handler: chain(syncCheckpointMetaHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/restore", Handler: chain(syncRestoreHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/ingest", Handler: chain(syncIngestHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodGet, Path: "/api/v1/admin/sync/channels/:id/inspect", Handler: chain(syncInspectHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
{Method: http.MethodPost, Path: "/api/v1/admin/sync/channels/:id/preview", Handler: chain(syncPreviewHandler(svcCtx), rl, authMW, tenant, perm(authx.Perm数据同步))},
@@ -431,6 +430,34 @@ func yuhengTicketExchangeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
}
func yuhengRestoreByHostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req applogic.YuhengRestoreByHostReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
resp, err := applogic.NewAuthLogic(r.Context(), svcCtx).RestoreByYuhengHost(req)
if err != nil {
if e := applogic.AsRestoreBindError(err); e != nil {
body := map[string]any{
"code": e.HTTPStatus,
"message": e.Message,
"ok": false,
}
if e.NeedBind {
body["need_bind"] = true
}
httpx.WriteJson(w, e.HTTPStatus, body)
return
}
authx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.OkJson(w, resp)
}
}
func roleListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
items, err := applogic.NewRoleAdminLogic(r.Context(), svcCtx).List()