144 lines
4.9 KiB
Go
144 lines
4.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
authsvc "server/services/auth"
|
|
"server/services/wechatmp"
|
|
)
|
|
|
|
// =============================================================
|
|
// 统一认证中心「微信扫码登录」(未认证公众号方案):
|
|
// 登录页展示「服务号关注二维码 + 6 位登录码」→ 用户微信扫码进入公众号会话
|
|
// → 发送登录码 → 服务号回复「确认登录」链接 → 用户点击(/mp-login 兑换时
|
|
// 携带 scene 回写确认)→ 本组接口轮询确认后建立 UAC 会话 Cookie,
|
|
// 前端跳回 authorize 完成 OAuth 发放。全程无极验验证码。
|
|
// =============================================================
|
|
|
|
// WechatScanStart POST /auth/wechat/scan/start
|
|
// 发起扫码登录会话并生成带参二维码(认证公众号);未认证回退普通关注二维码。
|
|
// 用户扫码后公众号自动回复「确认登录」链接,无需输入任何登录码。
|
|
func (c *AuthLoginController) WechatScanStart() {
|
|
scene, qrURL, notice, expireAt, err := wechatmp.StartScanLogin()
|
|
if err != nil {
|
|
c.serveJSON(map[string]interface{}{"code": 500, "msg": "发起扫码登录失败:" + err.Error()})
|
|
return
|
|
}
|
|
c.serveJSON(map[string]interface{}{
|
|
"code": 200,
|
|
"msg": "success",
|
|
"data": map[string]interface{}{
|
|
"scene": scene,
|
|
"qrcode_url": qrURL, // 带参二维码(认证公众号);空 = 已回退
|
|
"follow_qrcode": wechatmp.FollowQrcodeURL(),
|
|
"notice": notice, // 回退原因(非空时前端醒目提示)
|
|
"expire_in": int(time.Until(expireAt).Seconds()),
|
|
},
|
|
})
|
|
}
|
|
|
|
// WechatScanStatus GET /auth/wechat/scan/status?scene=xxx&redirect=yyy
|
|
// 登录页轮询:待确认 status=0;已确认 → 建立 UAC 会话(写 Cookie)并返回回跳地址
|
|
// (多企业时回跳企业选择步骤);过期 status=2。
|
|
func (c *AuthLoginController) WechatScanStatus() {
|
|
redirectParam := c.GetString("redirect")
|
|
clientID := strings.TrimSpace(c.GetString("client_id"))
|
|
row, err := wechatmp.PickScanLogin(c.GetString("scene"))
|
|
if err != nil {
|
|
if err == wechatmp.ErrScanLoginInvalid {
|
|
c.serveJSON(map[string]interface{}{"code": 200, "data": map[string]interface{}{"status": 2}})
|
|
return
|
|
}
|
|
// 待确认等业务提示:status=0 继续轮询
|
|
c.serveJSON(map[string]interface{}{"code": 200, "data": map[string]interface{}{"status": 0, "msg": err.Error()}})
|
|
return
|
|
}
|
|
|
|
// 身份状态校验(禁用/锁定则拒绝)
|
|
var identity models.AuthIdentity
|
|
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
|
|
Filter("id", row.BindID).One(&identity); err != nil || identity.Status != models.AuthIdentityStatusEnabled {
|
|
c.serveJSON(map[string]interface{}{"code": 400, "msg": "账号已被禁用或锁定,无法登录"})
|
|
return
|
|
}
|
|
|
|
// 企业选择:单企业直接进入;多企业进入登录页企业选择步骤
|
|
tenants, lerr := authsvc.ListTenantOptions(row.BindID)
|
|
if lerr != nil || len(tenants) == 0 {
|
|
c.serveJSON(map[string]interface{}{"code": 400, "msg": "该账号未加入任何企业,无法登录"})
|
|
return
|
|
}
|
|
tid := uint64(authsvc.PendingTenantID)
|
|
if len(tenants) == 1 {
|
|
tid = tenants[0].Tid
|
|
}
|
|
|
|
username := "微信用户"
|
|
if identity.Nickname != nil && strings.TrimSpace(*identity.Nickname) != "" {
|
|
username = *identity.Nickname
|
|
}
|
|
ip := c.Ctx.Input.IP()
|
|
userAgent := c.Ctx.Request.UserAgent()
|
|
|
|
// 建立 UAC 会话并写 Cookie(与账号密码登录同一套会话体系,authorize 据此发码)
|
|
sess, serr := authsvc.CreateSession(authsvc.SessionInfo{
|
|
IdentityID: row.BindID,
|
|
Tid: tid,
|
|
ClientID: clientID,
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
LoginType: "wechat_mp",
|
|
Amr: "wechat_mp",
|
|
})
|
|
if serr != nil {
|
|
c.serveJSON(map[string]interface{}{"code": 400, "msg": "登录失败:" + serr.Error()})
|
|
return
|
|
}
|
|
if _, terr := authsvc.IssueTokens(authsvc.TokenIssue{
|
|
IdentityID: row.BindID,
|
|
Tid: tid,
|
|
ClientID: clientID,
|
|
Sid: sess.Sid,
|
|
Username: username,
|
|
Amr: "wechat_mp",
|
|
}); terr != nil {
|
|
c.serveJSON(map[string]interface{}{"code": 500, "msg": "签发令牌失败:" + terr.Error()})
|
|
return
|
|
}
|
|
setSessionCookieForCtx(c.Ctx, sess.Sid)
|
|
|
|
// 登录日志(失败不影响主流程)
|
|
identityID := row.BindID
|
|
amr := "wechat_mp"
|
|
_, _ = models.Orm.Insert(&models.AuthLoginLog{
|
|
Tid: &tid,
|
|
IdentityID: &identityID,
|
|
UserName: username,
|
|
ClientID: clientID,
|
|
LoginType: "wechat_mp",
|
|
Amr: &amr,
|
|
Status: 1,
|
|
Message: "公众号扫码登录成功",
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
})
|
|
|
|
// 默认回跳到原 authorize;多企业时回跳登录页企业选择步骤
|
|
back := decodeRedirect(redirectParam)
|
|
if len(tenants) > 1 {
|
|
back = "/auth/login?step=tenant&client_id=" + url.QueryEscape(clientID) +
|
|
"&redirect=" + url.QueryEscape(redirectParam)
|
|
}
|
|
|
|
c.serveJSON(map[string]interface{}{
|
|
"code": 200,
|
|
"data": map[string]interface{}{
|
|
"status": 1,
|
|
"redirect": back,
|
|
},
|
|
})
|
|
}
|