feat(owner): 扫码绑定确认时自动把主人id登记进owner.yaml白名单(热生效)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
Aska committed 2026-09-08 02:53:01 +08:00
1 parent 0aa387f83c
commit 5a7ee9d772
7 files changed
+487 -65

No files matched your search

@@ -42,9 +42,9 @@ type weixinLoginAdmin interface {
// identifier (qrcode); the frontend renders a scannable QR from qrUrl, since the
// liteapp URL is an HTML page that cannot be fetched as an image.
type weixinLoginQRResponse struct {
QrUrl string `json:"qrUrl,omitempty"`
QrCode string `json:"qrCode,omitempty"`
Status string `json:"status"`
QrUrl string `json:"qrUrl,omitempty"`
QrCode string `json:"qrCode,omitempty"`
Status string `json:"status"`
}
// weixinLoginStatusResponse is the wire shape of GET /admin/channels/{platform}/login-status.
@@ -106,7 +106,7 @@ func (s *Server) adminWeixinLoginStatus(w http.ResponseWriter, r *http.Request)
ownerID, _ := cfg["owner_id"].(string)
resp.BotID = botID
resp.BotToken = maskToken(token)
if err := s.persistWeixinConfirmed(platform, accountID, token, botID, ownerID); err != nil {
if err := s.persistWeixinConfirmed(r.Context(), platform, accountID, token, botID, ownerID); err != nil {
s.log.Error("weixin login persist", "platform", platform, "err", err)
} else {
s.auditClaims(r, "channel.weixin.login.persist", "ok")
@@ -122,8 +122,13 @@ func (s *Server) adminWeixinLoginStatus(w http.ResponseWriter, r *http.Request)
// persistWeixinConfirmed writes the login-obtained credentials into the account's
// channels.json config (merging onto the user's curated base_url/cdn fields and
// preserving its allow/deny/limit lists) and hot-reloads the platform so the
// bridge connects the now-configured adapter.
func (s *Server) persistWeixinConfirmed(platform, accountID, token, botID, ownerID string) error {
// bridge connects the now-configured adapter. When ownerID is non-empty it also
// registers the scanner's bound ilink_user_id into the owner whitelist
// (owner.yaml) so the bridge recognizes it as the owner without a manual edit.
// The whitelist registration is best-effort: a failure is logged but does not
// fail the already-persisted channels.json, since the identity red line never
// blocks a successful bind.
func (s *Server) persistWeixinConfirmed(ctx context.Context, platform, accountID, token, botID, ownerID string) error {
if s.channels == nil {
return fmt.Errorf("weixin login: channel config store not injected")
}
@@ -183,7 +188,19 @@ func (s *Server) persistWeixinConfirmed(platform, accountID, token, botID, owner
if err := runtimeconfig.SaveChannels(s.channels.Path(), updated); err != nil {
return err
}
return s.channels.Reload(platform)
if err := s.channels.Reload(platform); err != nil {
return err
}
// Stamp the scanner (the bound owner) into the owner whitelist so the bridge
// recognizes it structurally. Best-effort: never fail an already-successful
// bind because a whitelist write hiccupped — the operator is alerted by a log
// line instead.
if ownerID != "" && s.ownerRegistrar != nil {
if err := s.ownerRegistrar.RegisterChannelOwner(ctx, platform, ownerID); err != nil {
s.log.Error("weixin login owner register", "platform", platform, "err", err)
}
}
return nil
}
// weixinAccountFor resolves the *weixin_ilink.Adapter registered for the