package identity import ( "net/http" "net/url" "strings" "aigateway.local/core/internal/platform/apiresponse" ) // registerSocial 注册扫码登录的公开与已认证端点。 // 公开入口复用 SSO 的 start/callback 路径(按 provider kind 分发); // 绑定/解绑/绑定列表挂在 portal 账号安全页面。 func (h *HTTPHandler) registerSocial() { h.mux.HandleFunc("POST /api/v1/portal/social/{kind}/bind/start", h.bindStart) h.mux.HandleFunc("DELETE /api/v1/portal/social/{kind}/bind", h.unbind) h.mux.HandleFunc("GET /api/v1/portal/social/bindings", h.bindings) } // startSocial 处理扫码登录的 start 分发(由 startSSO 按 kind 调用)。 func (h *HTTPHandler) startSocial(w http.ResponseWriter, r *http.Request) { provider, err := h.service.repository.GetSocialProviderByCode(r.Context(), r.PathValue("provider_code")) if err != nil || !provider.Enabled { http.NotFound(w, r) return } redirectURL, err := h.service.SocialLoginURL(r.Context(), provider.Kind, "login", "") if err != nil { apiresponse.Error(w, http.StatusBadRequest, err.Error()) return } http.Redirect(w, r, redirectURL, http.StatusFound) } // callbackSocial 处理扫码登录回调:成功后 302 回门户 return_url 并携带 // sso_code(登录)或 bind_result(绑定),失败携带 sso_error。 func (h *HTTPHandler) callbackSocial(w http.ResponseWriter, r *http.Request) { provider, err := h.service.repository.GetSocialProviderByCode(r.Context(), r.PathValue("provider_code")) if err != nil { apiresponse.Error(w, http.StatusBadRequest, "登录方式不存在") return } if r.URL.Query().Get("error") != "" { h.socialRedirect(w, r, provider, "sso_error", "企业登录已取消或拒绝") return } state := strings.TrimSpace(r.URL.Query().Get("state")) code := strings.TrimSpace(r.URL.Query().Get("code")) if state == "" || code == "" { h.socialRedirect(w, r, provider, "sso_error", "登录回调参数无效") return } result, err := h.service.CompleteSocialLogin(r.Context(), provider.Kind, state, code, SessionMeta{IP: h.service.ClientIP(r), UserAgent: r.UserAgent()}) if err != nil { h.socialRedirect(w, r, provider, "sso_error", err.Error()) return } switch result.Purpose { case "bind": if result.BindConflict { h.socialRedirect(w, r, provider, "bind_result", "conflict") return } h.socialRedirect(w, r, provider, "bind_result", "ok") case "login": h.socialRedirect(w, r, provider, "sso_code", result.SSOCode) } } // socialRedirect 302 到门户 return_url 并携带结果参数。 func (h *HTTPHandler) socialRedirect(w http.ResponseWriter, r *http.Request, provider SocialProvider, key, value string) { target, err := url.Parse(provider.PortalReturnURL) if err != nil { apiresponse.Error(w, http.StatusBadGateway, "门户返回地址无效") return } query := target.Query() query.Set(key, value) target.RawQuery = query.Encode() http.Redirect(w, r, target.String(), http.StatusFound) } // bindStart 已认证用户发起扫码绑定:返回跳转企业身份源的 URL。 func (h *HTTPHandler) bindStart(w http.ResponseWriter, r *http.Request) { account, ok := h.requireAccount(w, r, KindPortal) if !ok { return } redirectURL, err := h.service.SocialLoginURL(r.Context(), r.PathValue("kind"), "bind", account.ID) if err != nil { apiresponse.Error(w, http.StatusBadRequest, err.Error()) return } apiresponse.OK(w, map[string]string{"redirect_url": redirectURL}) } // unbind 解除扫码绑定(仅本人)。 func (h *HTTPHandler) unbind(w http.ResponseWriter, r *http.Request) { account, ok := h.requireAccount(w, r, KindPortal) if !ok { return } if err := h.service.UnbindProvider(r.Context(), account.ID, r.PathValue("kind")); err != nil { h.writeIdentityError(w, err) return } apiresponse.OK(w, map[string]bool{"unbound": true}) } // bindings 返回账号的扫码绑定列表。 func (h *HTTPHandler) bindings(w http.ResponseWriter, r *http.Request) { account, ok := h.requireAccount(w, r, KindPortal) if !ok { return } items, err := h.service.ProviderBindings(r.Context(), account.ID) if err != nil { h.writeIdentityError(w, err) return } apiresponse.OK(w, items) }