[DIP-1] 微信公众号二维码登录:本地部署与线上部署双端共用代码
All checks were successful
DevStar Studio CI Pipeline - dev branch / build-and-push-x86-64-docker-image (push) Successful in 8m14s
All checks were successful
DevStar Studio CI Pipeline - dev branch / build-and-push-x86-64-docker-image (push) Successful in 8m14s
* 恢复误删了的从未登录状态下的页面登录后跳转到当前页的功能 * secret是敏感信息,不能打印在日志里面 * fixed bug: 根据配置AppID和AppSecret来createPowerWechatApp * 完成本地部署微信二维码登录功能,用户设置绑定微信的功能本地测试正常 * 本地部署可以扫码跳转注册页面,尚未查询用户数据 * 本地部署的后端已经可以和devstar.cn上的微信代理API打通,但是还没有调用本地用户认证相关代码,功能上还不完整 * 优化了signin navbar前端显示逻辑,根据app.ini配置使能wechat qr和openid * 增加wechat配置项,以便同时支持直接和间接的微信二维码登录 * 恢复openid原有的初始配置方法 * 默认支持微信二维码登录(仅在安装配置上实现,功能上尚未实现),默认disabled openid * "Initial commit from " + gitURL + " ( " + sha1 + " ) "
This commit is contained in:
@@ -24,6 +24,7 @@ type PowerWechatOfficialAccountUserConfigType struct {
|
||||
|
||||
type OfficialAccountType struct {
|
||||
Enabled bool
|
||||
DefaultDomainName string
|
||||
UserConfig PowerWechatOfficialAccountUserConfigType
|
||||
TempQrExpireSeconds int
|
||||
PowerWechat *PowerWechatOfficialAccountType.OfficialAccount
|
||||
@@ -40,12 +41,20 @@ type OfficialAccountType struct {
|
||||
*/
|
||||
func loadWechatSettingsFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("wechat")
|
||||
Wechat.OfficialAccount.Enabled = sec.Key("ENABLED_WECHAT_QR_SIGNIN").MustBool(true)
|
||||
log.Info("ENABLED_WECHAT_QR_SIGNIN == '%b'", Wechat.OfficialAccount.Enabled)
|
||||
Wechat.OfficialAccount.DefaultDomainName = sec.Key("WECHAT_QR_SERVICE_DOMAIN_NAME").MustString("devstar.cn")
|
||||
|
||||
Wechat.OfficialAccount.UserConfig.AppID = sec.Key("WECHAT_OFFICIAL_ACCOUNT_APP_ID").MustString("")
|
||||
Wechat.OfficialAccount.UserConfig.AppSecret = sec.Key("WECHAT_OFFICIAL_ACCOUNT_APP_SECRET").MustString("")
|
||||
Wechat.OfficialAccount.UserConfig.RedisAddr = sec.Key("WECHAT_OFFICIAL_ACCOUNT_REDIS_ADDR").MustString("")
|
||||
Wechat.OfficialAccount.UserConfig.MessageToken = sec.Key("WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN").MustString("")
|
||||
Wechat.OfficialAccount.UserConfig.MessageAesKey = sec.Key("WECHAT_OFFICIAL_ACCOUNT_MESSAGE_AES_KEY").MustString("")
|
||||
createPowerWechatApp(Wechat.OfficialAccount.UserConfig)
|
||||
if Wechat.OfficialAccount.UserConfig.AppID != "" && Wechat.OfficialAccount.UserConfig.AppSecret != "" {
|
||||
log.Info("createPowerWechatApp AppID:%s ", Wechat.OfficialAccount.UserConfig.AppID)
|
||||
createPowerWechatApp(Wechat.OfficialAccount.UserConfig)
|
||||
}
|
||||
|
||||
Wechat.OfficialAccount.TempQrExpireSeconds = sec.Key("WECHAT_OFFICIAL_ACCOUNT_TEMP_QR_EXPIRE_SECONDS").MustInt(60)
|
||||
// 扫码后,最长注册时间:默认24小时
|
||||
Wechat.OfficialAccount.RegisterationExpireSeconds = sec.Key("WECHAT_OFFICIAL_ACCOUNT_REGISTERATION_EXPIRE_SECONDS").MustInt(86400)
|
||||
@@ -87,8 +96,5 @@ func createPowerWechatApp(userConfig PowerWechatOfficialAccountUserConfigType) {
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("创建微信工具类 PowerWechat 失败,请检查 modules/setting/wechat.go ")
|
||||
} else {
|
||||
Wechat.OfficialAccount.Enabled = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -307,6 +307,8 @@ federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar.
|
||||
disable_registration = Disable Self-Registration
|
||||
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
|
||||
allow_only_external_registration_popup = Allow Registration Only Through External Services
|
||||
wechat_qr_signin = Enable Wechat QR Code Sign-In
|
||||
wechat_qr_signin_popup = Enable user sign-in via Wechat QR Code.
|
||||
openid_signin = Enable OpenID Sign-In
|
||||
openid_signin_popup = Enable user sign-in via OpenID.
|
||||
openid_signup = Enable OpenID Self-Registration
|
||||
|
||||
@@ -306,6 +306,8 @@ federated_avatar_lookup_popup=启用 Federated Avatars 查找以使用开源的
|
||||
disable_registration=禁止用户自助注册
|
||||
disable_registration_popup=禁用用户自助注册。只有管理员才能创建新的用户帐户。
|
||||
allow_only_external_registration_popup=仅允许通过外部服务注册
|
||||
wechat_qr_signin=启用 微信二维码 登录
|
||||
wechat_qr_signin_popup=启用通过 微信二维码 登录
|
||||
openid_signin=启用 OpenID 登录
|
||||
openid_signin_popup=启用通过 OpenID 登录
|
||||
openid_signup=启用 OpenID 自助注册
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package official_account
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
Result "code.gitea.io/gitea/routers/entity"
|
||||
wechat_official_account_service "code.gitea.io/gitea/services/wechat/official_account"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// QrCheckCodeStatus 检查二维码扫描状态
|
||||
@@ -31,7 +32,6 @@ func QrCheckCodeStatus(responseWriter http.ResponseWriter, request *http.Request
|
||||
Result.RespFailedIllegalWechatQrTicket.RespondJson2HttpResponseWriter(responseWriter)
|
||||
return
|
||||
}
|
||||
|
||||
// 调用 WeChat Official Account Service 层,获取二维码扫码状态
|
||||
qrStatusVO, err := wechat_official_account_service.GetTempQRStatus(ticket)
|
||||
if err != nil {
|
||||
|
||||
@@ -153,6 +153,7 @@ func Install(ctx *context.Context) {
|
||||
|
||||
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
|
||||
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
|
||||
form.EnableWechatQRSignIn = true
|
||||
form.DisableRegistration = setting.Service.DisableRegistration
|
||||
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
|
||||
form.EnableCaptcha = setting.Service.EnableCaptcha
|
||||
@@ -445,7 +446,17 @@ func SubmitInstall(ctx *context.Context) {
|
||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||
return
|
||||
}
|
||||
|
||||
if form.EnableWechatQRSignIn {
|
||||
cfg.Section("wechat").Key("ENABLED_WECHAT_QR_SIGNIN").SetValue("true")
|
||||
// cfg.Section("wechat").Key("WECHAT_QR_SERVICE_DOMAIN_NAME").SetValue("devstar.cn")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_TEMP_QR_EXPIRE_SECONDS").SetValue("180")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_REGISTERATION_EXPIRE_SECONDS").SetValue("86400")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_APP_ID").SetValue("")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_APP_SECRET").SetValue("")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_REDIS_ADDR").SetValue("")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN").SetValue("")
|
||||
// cfg.Section("wechat").Key("WECHAT_OFFICIAL_ACCOUNT_MESSAGE_AES_KEY").SetValue("")
|
||||
}
|
||||
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
|
||||
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
|
||||
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
wechat_official_account_openid_model "code.gitea.io/gitea/models/wechat"
|
||||
wechat_official_account_cache_service "code.gitea.io/gitea/services/wechat/cache"
|
||||
wechat_official_account_service "code.gitea.io/gitea/services/wechat/official_account"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -15,6 +12,10 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
wechat_official_account_openid_model "code.gitea.io/gitea/models/wechat"
|
||||
wechat_official_account_cache_service "code.gitea.io/gitea/services/wechat/cache"
|
||||
wechat_official_account_service "code.gitea.io/gitea/services/wechat/official_account"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -167,6 +168,15 @@ func CheckAutoLogin(ctx *context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func SignInNavbarSetting(ctx *context.Context) {
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
ctx.Data["EnableWechatQrLogin"] = setting.Wechat.OfficialAccount.Enabled
|
||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
||||
ctx.Data["PageIsLogin"] = false
|
||||
ctx.Data["PageIsSignUp"] = false
|
||||
ctx.Data["PageIsWechatQrLogin"] = false
|
||||
}
|
||||
|
||||
// SignInWechatQr 渲染微信扫码登录页面
|
||||
func SignInWechatQr(ctx *context.Context) {
|
||||
if CheckAutoLogin(ctx) {
|
||||
@@ -177,14 +187,15 @@ func SignInWechatQr(ctx *context.Context) {
|
||||
RedirectAfterLogin(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
SignInNavbarSetting(ctx)
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
ctx.Data["SignInWechatQrLink"] = setting.AppSubURL + "/user/login/wechat/official-account"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLogin"] = true
|
||||
ctx.Data["PageIsWechatQrLogin"] = true
|
||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
||||
|
||||
_, err := GenerateWechatQr(ctx)
|
||||
wechatQrTicket, wechatQrCodeUrl, err := auth_service.GetWechatQRTicket(ctx)
|
||||
if err != nil {
|
||||
wechatQrFallbackLoginURL := "/user/login"
|
||||
redirectTo := ctx.FormString("redirect_to")
|
||||
@@ -196,6 +207,10 @@ func SignInWechatQr(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["wechatQrTicket"] = wechatQrTicket
|
||||
ctx.Data["wechatQrCodeUrl"] = wechatQrCodeUrl
|
||||
ctx.Data["wechatQrExpireSeconds"] = setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSignInWechatQr)
|
||||
}
|
||||
|
||||
@@ -212,6 +227,8 @@ func SignIn(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
SignInNavbarSetting(ctx)
|
||||
|
||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||
if err != nil {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
@@ -222,8 +239,6 @@ func SignIn(ctx *context.Context) {
|
||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLogin"] = true
|
||||
ctx.Data["PageIsPasswordLogin"] = true
|
||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
||||
|
||||
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
||||
context.SetCaptchaData(ctx)
|
||||
@@ -478,9 +493,10 @@ func SignOut(ctx *context.Context) {
|
||||
|
||||
// SignUp render the register page
|
||||
func SignUp(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("sign_up")
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("sign_up")
|
||||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
|
||||
SignInNavbarSetting(ctx)
|
||||
|
||||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
|
||||
if err != nil {
|
||||
|
||||
@@ -39,6 +39,8 @@ func SignInOpenID(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
SignInNavbarSetting(ctx)
|
||||
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLoginOpenID"] = true
|
||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
binaryUtils "encoding/binary"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Define a Wechat Error type message
|
||||
type WechatError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Implement the Error() method for the `WechatError` type
|
||||
func (e *WechatError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成微信临时二维码
|
||||
*
|
||||
* @param ctx 页面会话上下文环境
|
||||
* @return string 生成的微信二维码的 ticket
|
||||
* @return error 如果生成二维码过程中出现错误,则返回相应的错误信息
|
||||
*/
|
||||
func GenerateWechatQr(ctx *context.Context) (wechatQrTicket string, errorGenerateQr error) {
|
||||
|
||||
if setting.Wechat.OfficialAccount.PowerWechat == nil {
|
||||
log.Warn("PowerWechat工具类配置错误, 不会生成公众号带参数二维码")
|
||||
return "", &WechatError{message: "ERROR: PowerWechat 配置错误 (PowerWechat app instance has not yet been initialized!)"}
|
||||
}
|
||||
|
||||
// 生成随机 sceneStr 场景值
|
||||
// sceneStr生成规则:UUIDv4后边拼接 当前UnixNano时间戳转为byte数组后的Base64
|
||||
// e.g, sceneStr == "1c78e8d914fb4307a3588ac0f6bc092a@yPXAm+ve5hc="
|
||||
bytesArrayUnit64 := make([]byte, 8)
|
||||
binaryUtils.LittleEndian.PutUint64(bytesArrayUnit64, uint64(time.Now().UnixNano()))
|
||||
currentTimestampNanoBase64 := base64.StdEncoding.EncodeToString(bytesArrayUnit64)
|
||||
sceneStr := strings.ReplaceAll(uuid.New().String(), "-", "") + "@" + currentTimestampNanoBase64
|
||||
|
||||
qrExpireSeconds := setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||
qrData, err := setting.Wechat.OfficialAccount.PowerWechat.QRCode.Temporary(ctx, sceneStr, qrExpireSeconds)
|
||||
if err == nil {
|
||||
wechatQrTicket = qrData.Ticket
|
||||
ctx.Data["wechatQrTicket"] = wechatQrTicket
|
||||
ctx.Data["wechatQrCodeUrl"] = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + wechatQrTicket
|
||||
ctx.Data["wechatQrExpireSeconds"] = qrData.ExpireSeconds
|
||||
} else {
|
||||
log.Warn(" [!] 无法生成微信公众号带参数临时二维码: %s", err.Error())
|
||||
}
|
||||
return wechatQrTicket, err
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
web_auth_utils "code.gitea.io/gitea/routers/web/auth"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/db"
|
||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
@@ -42,11 +42,15 @@ func Account(ctx *context.Context) {
|
||||
loadAccountData(ctx)
|
||||
|
||||
// 界面原型:更新微信,展示公众号带参数二维码
|
||||
_, err := web_auth_utils.GenerateWechatQr(ctx)
|
||||
wechatQrTicket, wechatQrCodeUrl, err := auth_service.GetWechatQRTicket(ctx)
|
||||
if err != nil {
|
||||
log.Warn("微信创建二维码失败,跳过")
|
||||
}
|
||||
|
||||
ctx.Data["wechatQrTicket"] = wechatQrTicket
|
||||
ctx.Data["wechatQrCodeUrl"] = wechatQrCodeUrl
|
||||
ctx.Data["wechatQrExpireSeconds"] = setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsAccount)
|
||||
}
|
||||
|
||||
|
||||
200
services/auth/wechat_qr.go
Normal file
200
services/auth/wechat_qr.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
binaryUtils "encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
wechat_official_account_user_model "code.gitea.io/gitea/models/wechat"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
wechat_official_account_cache_service "code.gitea.io/gitea/services/wechat/cache"
|
||||
wechat_official_account_vo "code.gitea.io/gitea/services/wechat/vo"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Define a Wechat Error type message
|
||||
type WechatError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// Implement the Error() method for the `WechatError` type
|
||||
func (e *WechatError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
type ResponseData struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
Ticket string `json:"ticket"`
|
||||
ExpireSeconds int `json:"expire_seconds"`
|
||||
URL string `json:"url"`
|
||||
QRImageURL string `json:"qr_image_url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
/**
|
||||
* GetWechatQRTicket 生成微信官方账户临时二维码
|
||||
*
|
||||
* @return string 生成的微信二维码的 ticket
|
||||
* @return string 生成的微信二维码图片URL
|
||||
* @return error 如果生成二维码过程中出现错误,则返回相应的错误信息
|
||||
*/
|
||||
func GetWechatQRTicket(ctx *context.Context) (wechatQrTicket string, QRImageURL string, errorGenerateQr error) {
|
||||
// 生成随机 sceneStr 场景值
|
||||
// sceneStr生成规则:UUIDv4后边拼接 当前UnixNano时间戳转为byte数组后的Base64
|
||||
// e.g, sceneStr == "1c78e8d914fb4307a3588ac0f6bc092a@yPXAm+ve5hc="
|
||||
bytesArrayUnit64 := make([]byte, 8)
|
||||
binaryUtils.LittleEndian.PutUint64(bytesArrayUnit64, uint64(time.Now().UnixNano()))
|
||||
currentTimestampNanoBase64 := base64.StdEncoding.EncodeToString(bytesArrayUnit64)
|
||||
sceneStr := strings.ReplaceAll(uuid.New().String(), "-", "") + "@" + currentTimestampNanoBase64
|
||||
|
||||
qrExpireSeconds := setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||
|
||||
// 构建请求的 URL
|
||||
url := fmt.Sprintf("https://%s/api/wechat/official-account/login/qr/generate?qrExpireSeconds=%d&sceneStr=%s", setting.Wechat.OfficialAccount.DefaultDomainName, qrExpireSeconds, sceneStr)
|
||||
|
||||
// 发送 GET 请求
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to send GET request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to read response body: %v", err)
|
||||
}
|
||||
// 检查响应状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", "", fmt.Errorf("received non-200 response status: %s", resp.Status)
|
||||
}
|
||||
|
||||
// 解析 JSON 响应
|
||||
var data ResponseData
|
||||
if err := json.Unmarshal(bodyBytes, &data); err != nil {
|
||||
return "", "", fmt.Errorf("failed to unmarshal JSON response: %v", err)
|
||||
}
|
||||
quit := make(chan bool) // 用来通知goroutine停止
|
||||
// 启动一个新的goroutine来进行轮询
|
||||
go func() {
|
||||
// 创建一个超时定时器通道
|
||||
timeout := time.After(time.Duration(qrExpireSeconds) * time.Second)
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
checkWechatQrTicketStatus(ctx, wechatQrTicket, quit)
|
||||
|
||||
case <-quit:
|
||||
log.Info("Stopping polling...")
|
||||
return
|
||||
|
||||
case <-timeout:
|
||||
log.Info("Polling timed out after", qrExpireSeconds, "seconds.")
|
||||
quit <- true // 发送停止信号给轮询goroutine
|
||||
}
|
||||
}
|
||||
}()
|
||||
return data.Data.Ticket, data.Data.QRImageURL, nil
|
||||
}
|
||||
|
||||
// Response represents the top-level JSON structure
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data Data `json:"data"`
|
||||
}
|
||||
|
||||
// Data represents the "data" object in the JSON
|
||||
type Data struct {
|
||||
IsScanned bool `json:"is_scanned"`
|
||||
SceneStr string `json:"scene_str"`
|
||||
OpenID string `json:"openid"`
|
||||
IsBinded bool `json:"is_binded"`
|
||||
}
|
||||
|
||||
// 假设这是用于检查二维码状态的函数
|
||||
func checkWechatQrTicketStatus(ctx *context.Context, qrTicket string, quit chan bool) {
|
||||
|
||||
url := fmt.Sprintf("https://%s/api/wechat/official-account/login/qr/check-status?ticket=%s&_=%d",
|
||||
setting.Wechat.OfficialAccount.DefaultDomainName, qrTicket, time.Now().UnixMilli())
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Error("There was a problem with the fetch operation:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Error("Network response was not ok")
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error("Error reading response body:", err)
|
||||
return
|
||||
}
|
||||
// 解析 JSON 响应
|
||||
var data Response
|
||||
if err := json.Unmarshal(bodyBytes, &data); err != nil {
|
||||
log.Error("failed to unmarshal JSON response: %v", err)
|
||||
return
|
||||
}
|
||||
if data.Code == 0 && data.Data.IsScanned {
|
||||
log.Info("Caching WeChat QR Scanned Info: %s\n", bodyBytes)
|
||||
// {"code":0,"msg":"操作成功","data":{
|
||||
// "is_scanned":true,
|
||||
// "scene_str":"2d521f80047c42aba27ee9beade35985@p2Z6hfheDxg=",
|
||||
// "openid":"oQowJ6cD9WSuoxYaCc7mryfn-lVo",
|
||||
// "is_binded":true}}
|
||||
// 准备扫码状态VO对象
|
||||
qrStatusVO := wechat_official_account_vo.GetWechatOfficialAccountTempQRStatusVO{
|
||||
SceneStr: data.Data.SceneStr,
|
||||
IsScanned: data.Data.IsScanned,
|
||||
OpenId: data.Data.OpenID,
|
||||
}
|
||||
// 从微信服务器消息推送中解析扫码人的 OpenID
|
||||
_, err := wechat_official_account_user_model.QueryUserByOpenid(ctx, qrStatusVO.OpenId)
|
||||
if err != nil {
|
||||
// 未找到 OpenID 对应的 DevStar 用户信息,提示前端导向注册页
|
||||
qrStatusVO.IsBinded = false
|
||||
qrStatusVOString, err := qrStatusVO.Marshal2JSONString()
|
||||
if err == nil {
|
||||
// 将扫码人的微信公众号 OpenID 标记为等待注册,等待时间用户注册完成,默认24小时
|
||||
// key: qrScanResponseDigest.Ticket
|
||||
// value: JSON 字符串
|
||||
// TTL: setting.Wechat.OfficialAccount.TempQrExpireSeconds
|
||||
wechat_official_account_cache_service.SetWechatOfficialAccountQrTicketWithTTL(
|
||||
qrTicket,
|
||||
qrStatusVOString,
|
||||
setting.Wechat.OfficialAccount.RegisterationExpireSeconds)
|
||||
}
|
||||
quit <- true
|
||||
return
|
||||
}
|
||||
|
||||
qrStatusVO.IsBinded = true
|
||||
qrVOJsonString, err := qrStatusVO.Marshal2JSONString()
|
||||
if err == nil {
|
||||
wechat_official_account_cache_service.SetWechatOfficialAccountQrTicketWithTTL(
|
||||
qrTicket,
|
||||
qrVOJsonString,
|
||||
setting.Wechat.OfficialAccount.TempQrExpireSeconds)
|
||||
}
|
||||
quit <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||
ctx.Data["EnableWechatQrLogin"] = setting.Wechat.OfficialAccount.Enabled
|
||||
ctx.Data["Link"] = ctx.Link
|
||||
|
||||
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
||||
|
||||
@@ -52,6 +52,7 @@ type InstallForm struct {
|
||||
EnableFederatedAvatar bool
|
||||
EnableOpenIDSignIn bool
|
||||
EnableOpenIDSignUp bool
|
||||
EnableWechatQRSignIn bool
|
||||
DisableRegistration bool
|
||||
AllowOnlyExternalRegistration bool
|
||||
EnableCaptcha bool
|
||||
|
||||
@@ -362,7 +362,7 @@ func GenerateGitContentFromGitURL(ctx context.Context, gitURL, commitID string,
|
||||
log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
|
||||
return fmt.Errorf("git remote add: %w", err)
|
||||
}
|
||||
commitMessage := "Initial commit from " + gitURL + "(" + sha1 + ")"
|
||||
commitMessage := "Initial commit from " + gitURL + " ( " + sha1 + " ) "
|
||||
initRepoCommit(ctx, tmpDir, repo, repo.Owner, repo.DefaultBranch, commitMessage)
|
||||
|
||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
{{svg "octicon-person"}} {{ctx.Locale.Tr "register"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login/wechat/official-account{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}">
|
||||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login{{if .EnableWechatQrLogin}}/wechat/official-account{{end}}{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}">
|
||||
{{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
@@ -221,12 +221,6 @@
|
||||
<input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="enable-openid-signin">
|
||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signin_popup"}}">{{ctx.Locale.Tr "install.openid_signin"}}</label>
|
||||
<input name="enable_open_id_sign_in" type="checkbox" {{if .enable_open_id_sign_in}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="disable-registration">
|
||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.disable_registration_popup"}}">{{ctx.Locale.Tr "install.disable_registration"}}</label>
|
||||
@@ -245,6 +239,18 @@
|
||||
<input name="enable_open_id_sign_up" type="checkbox" {{if .enable_open_id_sign_up}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="enable-openid-signin">
|
||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.openid_signin_popup"}}">{{ctx.Locale.Tr "install.openid_signin"}}</label>
|
||||
<input name="enable_open_id_sign_in" type="checkbox" {{if .enable_open_id_sign_in}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="enable-wechat-qr-signin">
|
||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.wechat_qr_signin_popup"}}">{{ctx.Locale.Tr "install.wechat_qr_signin"}}</label>
|
||||
<input name="enable_wechat_qr_sign_in" type="checkbox" {{if .enable_wechat_qr_sign_in}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox" id="enable-captcha">
|
||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.enable_captcha_popup"}}">{{ctx.Locale.Tr "install.enable_captcha"}}</label>
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
{{if or .EnableOpenIDSignIn .EnableSSPI .EnableWechatQrLogin}}
|
||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar secondary-nav">
|
||||
<div class="overflow-menu-items tw-justify-center">
|
||||
|
||||
{{/* 微信扫码登录 */}}
|
||||
{{if .PageIsLogin}}
|
||||
<a class="{{if .PageIsWechatQrLogin}} active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/wechat/official-account">
|
||||
<a class="{{if .PageIsLogin}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
||||
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSignUp}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{ctx.Locale.Tr "auth.create_new_account"}}
|
||||
</a>
|
||||
{{if .EnableWechatQrLogin}}
|
||||
<a class="{{if .PageIsWechatQrLogin}}active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/wechat/official-account">
|
||||
{{ctx.Locale.Tr "settings.wechat_qr_login"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="{{if .PageIsLoginOpenID}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login/openid">
|
||||
{{svg "fontawesome-openid"}}
|
||||
OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{/* 2. 密码登录 */}}
|
||||
{{if .PageIsLogin}}
|
||||
<a class="{{if .PageIsPasswordLogin}} active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
||||
{{ctx.Locale.Tr "password"}}
|
||||
{{if .EnableSSPI}}
|
||||
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||
{{svg "fontawesome-windows"}}
|
||||
SSPI
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{/* 3. 手机短信登录 */}}
|
||||
{{/* <a class="{{if .PageIsSmsLogin}} active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/sms"> */}}
|
||||
{{/* {{ctx.Locale.Tr "settings.phone_sms_code"}} */}}
|
||||
{{/* </a> */}}
|
||||
|
||||
{{if or .EnableOpenIDSignIn .EnableSSPI}}
|
||||
<a class="{{if .PageIsLogin}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
||||
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSignUp}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{ctx.Locale.Tr "auth.create_new_account"}}
|
||||
</a>
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="{{if .PageIsLoginOpenID}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login/openid">
|
||||
{{svg "fontawesome-openid"}}
|
||||
OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableSSPI}}
|
||||
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||
{{svg "fontawesome-windows"}}
|
||||
SSPI
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</overflow-menu>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user